From 9710b3d5709320a96e365e65589f9e16d81c6eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jochum?= Date: Wed, 21 Sep 2022 01:26:55 +0200 Subject: [PATCH] Initial commit --- .gitignore | 8 + LICENSE | 13 + README.md | 47 + Taskfile.yml | 98 ++ cmd/microauthsqld/config/config.go | 71 ++ cmd/microauthsqld/main.go | 129 +++ .../migrations/postgres/.gitkeep | 0 cmd/microauthsqld/plugins.go | 8 + docker/microauthsqld/Dockerfile | 29 + docs/implementation.drawio | 46 + docs/implementation.png | Bin 0 -> 85082 bytes go.mod | 106 ++ go.sum | 356 +++++++ internal/bun/bun.go | 118 +++ internal/config/load.go | 75 ++ internal/logger/logger.go | 79 ++ internal/proto/authpb/authpb.pb.go | 996 ++++++++++++++++++ internal/proto/authpb/authpb.pb.micro.go | 215 ++++ internal/proto/authpb/authpb.proto | 88 ++ internal/util/goroutine.go | 22 + internal/util/retry.go | 46 + internal/util/serviceregistry.go | 73 ++ internal/util/token.go | 31 + internal/util/util.go | 22 + noop.go | 115 ++ plugin.go | 63 ++ plugins/client/jwt/jwt.go | 131 +++ plugins/router/jwt/jwt.go | 129 +++ registry.go | 90 ++ 29 files changed, 3204 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Taskfile.yml create mode 100644 cmd/microauthsqld/config/config.go create mode 100644 cmd/microauthsqld/main.go create mode 100644 cmd/microauthsqld/migrations/postgres/.gitkeep create mode 100644 cmd/microauthsqld/plugins.go create mode 100644 docker/microauthsqld/Dockerfile create mode 100644 docs/implementation.drawio create mode 100644 docs/implementation.png create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/bun/bun.go create mode 100644 internal/config/load.go create mode 100644 internal/logger/logger.go create mode 100644 internal/proto/authpb/authpb.pb.go create mode 100644 internal/proto/authpb/authpb.pb.micro.go create mode 100644 internal/proto/authpb/authpb.proto create mode 100644 internal/util/goroutine.go create mode 100644 internal/util/retry.go create mode 100644 internal/util/serviceregistry.go create mode 100644 internal/util/token.go create mode 100644 internal/util/util.go create mode 100644 noop.go create mode 100644 plugin.go create mode 100644 plugins/client/jwt/jwt.go create mode 100644 plugins/router/jwt/jwt.go create mode 100644 registry.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd6cb06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_STORE + +.task/ + +!.gitkeep + +go.work +go.work.sum \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3d2997 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2022 René Jochum + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f8965cd --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +[![Build Status](https://drone.fk.jochum.dev/api/badges/jo-micro/auth2/status.svg)](https://drone.fk.jochum.dev/jo-micro/auth2) [![Go Reference](https://pkg.go.dev/badge/jochum.dev/jo-micro/auth2.svg)](https://pkg.go.dev/jochum.dev/jo-micro/auth2) + +# auth2 + +An auth provider for go-micro, it get's users from a postgres database, in the future maybe from other SQL Databases supported by [bun](https://bun.uptrace.dev/) as well. + +It registers itself with [router](https://jochum.dev/jo-micro/router), if you use it in your stack. + +## JWT Token Auth + +### Generate keys + +```bash +task keys +``` + +## Developers corner + +### Build podman/docker image + +#### Prerequesits + +- podman +- [Task](https://taskfile.dev/#/installation) + +#### Build + +```bash +task +``` + +#### Remove everything + +```bash +task rm +``` + +## Authors + +- René Jochum - rene@jochum.dev + +## License + +Its dual licensed: + +- Apache-2.0 +- GPL-2.0-or-later diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..2bdab66 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,98 @@ +version: '3' + +vars: + GIT_TAG: + sh: git tag --points-at HEAD + GIT_COMMIT: + sh: git rev-parse --short HEAD + GIT_DIRTY: + sh: git status -s + VERSION: + sh: if test "{{.GIT_DIRTY}}" != ""; then echo "{{.GIT_COMMIT}}-dirty"; elif test "{{.GIT_TAG}}" != ""; then echo "{{.GIT_TAG}}"; else echo "{{.GIT_COMMIT}}"; fi + +tasks: + default: + cmds: + - task: version + - task: volume + - task: podman + + version: + desc: Print the version optained from git + cmds: + - echo "{{.VERSION}}" + + volume: + run: "once" + cmds: + - podman volume inspect jo-micro_auth_go 1>/dev/null 2>&1 || podman volume create jo-micro_auth_go + + builder: + desc: Run something in the builder container for example "task builder -- go get -u ./..." + cmds: + - podman run --rm -v "{{.VOLUME_PATH}}:/go:rw" -v "$PWD:/code" registry.fk.jochum.dev/jo-micro/builder:latest {{.CLI_ARGS}} + vars: + VOLUME_PATH: + sh: podman volume inspect jo-micro_auth_go --format "{{"{{"}}.Mountpoint{{"}}"}}" + + protoc: + run: "once" + desc: Generate protobruf go files + sources: + - ./proto/**/*.proto + cmds: + - task: builder + vars: + CLI_ARGS: /scripts/protoc_gen.sh + + build:authsql: + deps: + - protoc + sources: + - ./go.sum + - ./*.go + - ./service/microauthsqld/**/*.go + - ./internal/**/*.go + cmds: + - podman build -v "{{.VOLUME_PATH}}:/go:rw" --build-arg VERSION={{.VERSION}} -t registry.fk.jochum.dev/jo-micro/auth2-sql:latest -f ./docker/microauthsqld/Dockerfile . + vars: + VOLUME_PATH: + sh: podman volume inspect jo-micro_auth_go --format "{{"{{"}}.Mountpoint{{"}}"}}" + + podman: + desc: Generate docker container for jo-micro/auth-sql tagged as registry.fk.jochum.dev/jo-micro/auth2-sql:latest + cmds: + - task: build:authsql + + tidy: + desc: Run "go mod tidy" in a container + cmds: + - task: builder + vars: + CLI_ARGS: go mod tidy + + update: + desc: Run "go get -u ./..." in a container + cmds: + - task: builder + vars: + CLI_ARGS: /scripts/upgrade_deps.sh + + fmt: + desc: Run "go fmt ./..." in a container + cmds: + - task: builder + vars: + CLI_ARGS: go fmt ./... + + keys: + desc: Generate keys + cmds: + - podman run registry.fk.jochum.dev/jo-micro/auth2-sql:latest microauthsqld --generate-keys + + rm: + desc: Remove all persistent data + cmds: + - podman volume rm jo-micro_auth_go || exit 0 + - podman image rm registry.fk.jochum.dev/jo-micro/auth2-sql:latest || exit 0 + - rm -rf $PWD/.task \ No newline at end of file diff --git a/cmd/microauthsqld/config/config.go b/cmd/microauthsqld/config/config.go new file mode 100644 index 0000000..3198f56 --- /dev/null +++ b/cmd/microauthsqld/config/config.go @@ -0,0 +1,71 @@ +package config + +var ( + Version = "0.0.1-dev0" +) + +const ( + Name = "go.micro.auth" + PkgPath = "jochum.dev/jo-micro/auth2" +) + +const ( + EnvDev = "dev" + EnvProd = "prod" +) + +type Config struct { + Auth AuthConfig +} + +type ServerConfig struct { + Env string + RouterURI string +} + +type TokenKeys struct { + PrivKey string + PubKey string +} + +type AuthConfig struct { + Server ServerConfig + + RefreshTokenExpiry int + AccessTokenExpiry int + AccessToken TokenKeys + RefreshToken TokenKeys +} + +func GetConfig() *Config { + return &_cfg +} + +func GetServerConfig() ServerConfig { + return _cfg.Auth.Server +} + +func GetAuthConfig() AuthConfig { + return _cfg.Auth +} + +// internal instance of Config with defaults +var _cfg = Config{ + Auth: AuthConfig{ + Server: ServerConfig{ + Env: EnvProd, + RouterURI: "auth", + }, + + RefreshTokenExpiry: 86400 * 14, // 14 days + AccessTokenExpiry: 900, // 15 minutes + AccessToken: TokenKeys{ + PrivKey: "", + PubKey: "", + }, + RefreshToken: TokenKeys{ + PrivKey: "", + PubKey: "", + }, + }, +} diff --git a/cmd/microauthsqld/main.go b/cmd/microauthsqld/main.go new file mode 100644 index 0000000..3fc5d56 --- /dev/null +++ b/cmd/microauthsqld/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" + "log" + "os" + "os/exec" + "strings" + + "github.com/urfave/cli/v2" + "go-micro.dev/v4" + "go-micro.dev/v4/logger" + + "jochum.dev/jo-micro/auth2/cmd/microauthsqld/config" + iconfig "jochum.dev/jo-micro/auth2/internal/config" + iLogger "jochum.dev/jo-micro/auth2/internal/logger" + "jochum.dev/jo-micro/router" + + "jochum.dev/jo-micro/auth2/internal/bun" +) + +var ( + ErrorNoKeys = errors.New("config AUTH_ACCESS_TOKEN_*_KEY or AUTH_REFRESH_TOKEN_*_KEY not given") +) + +func main() { + if err := iconfig.Load(config.GetConfig()); err != nil { + logger.Fatal(err) + } + + srv := micro.NewService() + + flags := []cli.Flag{ + &cli.BoolFlag{ + Name: "generate-keys", + Usage: "Generate keys for the config and/or the environment", + Value: false, + }, + } + flags = append(flags, bun.Flags()...) + flags = append(flags, iLogger.Flags()...) + + opts := []micro.Option{ + micro.Name(config.Name), + micro.Version(config.Version), + micro.Flags(flags...), + micro.Action(func(c *cli.Context) error { + if c.Bool("generate-keys") { + // Just generate keys and print them to the commandline + aPubKeyB, aPrivKeyB, err := ed25519.GenerateKey(nil) + if err != nil { + log.Fatal(err) + return err + } + rPubKeyB, rPrivKeyB, err := ed25519.GenerateKey(nil) + if err != nil { + log.Fatal(err) + return err + } + + absPath, err := exec.LookPath(os.Args[0]) + if err != nil { + // Don't fail here + absPath = os.Args[0] + } + + fmt.Printf("# go.micro.auth ed25519 JWT keys - generated using '%s %s'\n", absPath, strings.Join(os.Args[1:len(os.Args)], " ")) + fmt.Printf("AUTH_ACCESSTOKEN_PRIVKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(aPrivKeyB)) + fmt.Printf("AUTH_ACCESSTOKEN_PUBKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(aPubKeyB)) + fmt.Printf("AUTH_REFRESHTOKEN_PRIVKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(rPrivKeyB)) + fmt.Printf("AUTH_REFRESHTOKEN_PUBKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(rPubKeyB)) + + os.Exit(0) + } + + // Start the logger + if err := iLogger.Start(c); err != nil { + log.Fatal(err) + return err + } + + // Connect to the database + if err := bun.Start(c); err != nil { + log.Fatal(err) + return err + } + + // Check if we got keys + authConfig := config.GetAuthConfig() + if authConfig.AccessToken.PrivKey == "" || authConfig.AccessToken.PubKey == "" || authConfig.RefreshToken.PrivKey == "" || authConfig.RefreshToken.PubKey == "" { + log.Fatal(ErrorNoKeys) + return ErrorNoKeys + } + + // Register with https://jochum.dev/jo-micro/router + r := router.NewHandler( + config.GetServerConfig().RouterURI, + router.NewRoute( + router.Method(router.MethodGet), + router.Path("/routes"), + router.Endpoint("routes"), + ), + ) + r.RegisterWithServer(srv.Server()) + + return nil + }), + } + + srv.Init(opts...) + + // Run server + if err := srv.Run(); err != nil { + logger.Fatal(err) + } + + // Disconnect from the database + if err := bun.Stop(); err != nil { + logger.Fatal(err) + } + + // Stop the logger + if err := iLogger.Stop(); err != nil { + logger.Fatal(err) + } +} diff --git a/cmd/microauthsqld/migrations/postgres/.gitkeep b/cmd/microauthsqld/migrations/postgres/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cmd/microauthsqld/plugins.go b/cmd/microauthsqld/plugins.go new file mode 100644 index 0000000..15fd8a0 --- /dev/null +++ b/cmd/microauthsqld/plugins.go @@ -0,0 +1,8 @@ +package main + +import ( + _ "github.com/go-micro/plugins/v4/broker/nats" + _ "github.com/go-micro/plugins/v4/registry/nats" + _ "github.com/go-micro/plugins/v4/transport/grpc" + _ "github.com/go-micro/plugins/v4/transport/nats" +) diff --git a/docker/microauthsqld/Dockerfile b/docker/microauthsqld/Dockerfile new file mode 100644 index 0000000..fae756f --- /dev/null +++ b/docker/microauthsqld/Dockerfile @@ -0,0 +1,29 @@ +# STEP 1 build executable binary +FROM registry.fk.jochum.dev/jo-micro/builder:latest AS builder + +# Create appuser (/etc/passwd entry for the runner container) +RUN useradd appuser + +ARG VERSION +COPY . /code/ +WORKDIR /code +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -installsuffix cgo -ldflags="-w -s -X 'jochum.dev/jo-micro/auth2/cmd/microauthsqld/config.Version=$VERSION'" -o /usr/local/bin/microauthsqld jochum.dev/jo-micro/auth2/cmd/microauthsqld + +# STEP 2 build a small image +# start from busybox +FROM busybox + +LABEL maintainer="René Jochum " + +# Copy certs, passwd and binary from builder +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=builder /etc/passwd /etc/passwd +COPY --from=builder /usr/local/bin/microauthsqld /usr/local/bin/microauthsqld +RUN chmod +x /usr/local/bin/microauthsqld + +COPY ./cmd/microauthsqld/migrations /migrations + +# Run as appuser +USER appuser + +CMD [ "/usr/local/bin/microauthsqld" ] \ No newline at end of file diff --git a/docs/implementation.drawio b/docs/implementation.drawio new file mode 100644 index 0000000..f767d5d --- /dev/null +++ b/docs/implementation.drawio @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/implementation.png b/docs/implementation.png new file mode 100644 index 0000000000000000000000000000000000000000..16205674e5a9dda5885d2cc84ab77780f9cd0a41 GIT binary patch literal 85082 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

Ta{!l|0r&;33xU84DRM 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 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f4014b --- /dev/null +++ b/go.mod @@ -0,0 +1,106 @@ +module jochum.dev/jo-micro/auth2 + +go 1.19 + +require ( + github.com/avast/retry-go v3.0.0+incompatible + github.com/go-micro/plugins/v4/broker/nats v1.1.1-0.20220908125827-e0369dde429b + github.com/go-micro/plugins/v4/config/encoder/toml v1.1.0 + github.com/go-micro/plugins/v4/config/encoder/yaml v1.1.0 + github.com/go-micro/plugins/v4/logger/logrus v1.1.0 + github.com/go-micro/plugins/v4/registry/nats v1.1.1-0.20220908125827-e0369dde429b + github.com/go-micro/plugins/v4/transport/grpc v1.1.0 + github.com/go-micro/plugins/v4/transport/nats v1.1.1-0.20220908125827-e0369dde429b + github.com/golang-jwt/jwt/v4 v4.4.2 + github.com/golang-migrate/migrate v3.5.4+incompatible + github.com/google/uuid v1.3.0 + github.com/jackc/pgx v3.6.2+incompatible + github.com/pkg/errors v0.9.1 + github.com/sirupsen/logrus v1.9.0 + github.com/uptrace/bun v1.1.8 + github.com/uptrace/bun/dialect/pgdialect v1.1.8 + github.com/uptrace/bun/extra/bundebug v1.1.8 + github.com/urfave/cli/v2 v2.16.3 + go-micro.dev/v4 v4.8.1 + google.golang.org/protobuf v1.28.1 + jochum.dev/jo-micro/router v0.2.3 +) + +require ( + github.com/BurntSushi/toml v1.2.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/bitly/go-simplejson v0.5.0 // indirect + github.com/cloudflare/circl v1.2.0 // indirect + github.com/cockroachdb/apd v1.1.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.8.1 // indirect + github.com/go-acme/lego/v4 v4.8.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect + github.com/gobwas/ws v1.1.0 // indirect + github.com/goccy/go-json v0.9.11 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/gorilla/handlers v1.5.1 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/compress v1.15.10 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/lib/pq v1.10.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/miekg/dns v1.1.50 // indirect + github.com/minio/highwayhash v1.0.2 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nats-io/jwt/v2 v2.3.0 // indirect + github.com/nats-io/nats.go v1.17.0 // indirect + github.com/nats-io/nkeys v0.3.0 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xanzy/ssh-agent v0.3.2 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect + golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect + golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa // indirect + google.golang.org/grpc v1.49.0 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..9275fd0 --- /dev/null +++ b/go.sum @@ -0,0 +1,356 @@ +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= +github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= +github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-acme/lego/v4 v4.8.0 h1:XienkuT6ZKHe0DE/LXeGP4ZY+ft+7ZMlqtiJ7XJs2pI= +github.com/go-acme/lego/v4 v4.8.0/go.mod h1:MXCdgHuQh25bfi/tPpyOV/9k2p1JVu6oxXcylAwkouI= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-log/log v0.2.0 h1:z8i91GBudxD5L3RmF0KVpetCbcGWAV7q1Tw1eRwQM9Q= +github.com/go-micro/plugins/v4/broker/nats v1.1.1-0.20220908125827-e0369dde429b h1:hzQUmqhw+g+qEcMXfZFCR28ASgjnFTbI24KJFSk7+4Y= +github.com/go-micro/plugins/v4/broker/nats v1.1.1-0.20220908125827-e0369dde429b/go.mod h1:dJ5fMD5/7rK1/DNtyWzI/5kGtjYzB1DwMlANr7b2ar4= +github.com/go-micro/plugins/v4/config/encoder/toml v1.1.0 h1:2inKk4YPjzUKvQEzmFT1ecQ4N7aAKQzF6tnOsR+rA9c= +github.com/go-micro/plugins/v4/config/encoder/toml v1.1.0/go.mod h1:QVbmOF267EAet8mWqbxHr7vGd6h9I9Aw6dZ7+/6v2a8= +github.com/go-micro/plugins/v4/config/encoder/yaml v1.1.0 h1:8G9f9WfdN8zzoEznAv5dOtMCh9R9RMdC3isUhDwe6Ro= +github.com/go-micro/plugins/v4/config/encoder/yaml v1.1.0/go.mod h1:NB58gTmSYwibr+jZQOPDj/WoeGXSCcHTvQi+UcdDNxw= +github.com/go-micro/plugins/v4/logger/logrus v1.1.0 h1:qCWt1eW2GiQnwivrBBcPRZbCwQ51A5SGUXptpxmGcuQ= +github.com/go-micro/plugins/v4/logger/logrus v1.1.0/go.mod h1:aNJaayU7YKqlxwM91B3NixYpGgtCk1HiSgwuvCb7co0= +github.com/go-micro/plugins/v4/registry/nats v1.1.1-0.20220908125827-e0369dde429b h1:9XdSUFJq2pR3QQquzpjiyHRBzHahvmQrwATzBtIyM2w= +github.com/go-micro/plugins/v4/registry/nats v1.1.1-0.20220908125827-e0369dde429b/go.mod h1:WdofRVBX8dt+b7J01JpryfAZ3HDD3JDs5t0NXWmBVmI= +github.com/go-micro/plugins/v4/transport/grpc v1.1.0 h1:mXfDYfFQLnVDzjGY3o84oe4prfux9h8txsnA19dKsj8= +github.com/go-micro/plugins/v4/transport/grpc v1.1.0/go.mod h1:J5xMp70xXZzm8yafICrDrWaUDd8Gwy8vt0xif7NcOPg= +github.com/go-micro/plugins/v4/transport/nats v1.1.1-0.20220908125827-e0369dde429b h1:JhY/Le8oAnNpV9k19zkAziU7c2JRbvcGL3c4oT1Qu2o= +github.com/go-micro/plugins/v4/transport/nats v1.1.1-0.20220908125827-e0369dde429b/go.mod h1:V241pJ//Xf5x9kyfQbEK7pf5ivH7vPEZRGRAcVCRVG0= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA= +github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA= +github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= +github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo= +github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= +github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/jwt/v2 v2.3.0 h1:z2mA1a7tIf5ShggOFlR1oBPgd6hGqcDYsISxZByUzdI= +github.com/nats-io/jwt/v2 v2.3.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.3.1 h1:tR8rp+ohGbqPGHROuzlvNzvw4G5TTeA5jUkxnSTEYPE= +github.com/nats-io/nats.go v1.17.0 h1:1jp5BThsdGlN91hW0k3YEfJbfACjiOYtUiLXG0RL4IE= +github.com/nats-io/nats.go v1.17.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/uptrace/bun v1.1.8 h1:slxuaP4LYWFbPRUmTtQhfJN+6eX/6ar2HDKYTcI50SA= +github.com/uptrace/bun v1.1.8/go.mod h1:iT89ESdV3uMupD9ixt6Khidht+BK0STabK/LeZE+B84= +github.com/uptrace/bun/dialect/pgdialect v1.1.8 h1:wayJhjYDPGv8tgOBLolbBtSFQ0TihFoo8E1T129UdA8= +github.com/uptrace/bun/dialect/pgdialect v1.1.8/go.mod h1:nNbU8PHTjTUM+CRtGmqyBb9zcuRAB8I680/qoFSmBUk= +github.com/uptrace/bun/extra/bundebug v1.1.8 h1:RrZNOYYFb690k14nCN0t/hokfpsgoppT55/Xk/ijvBA= +github.com/uptrace/bun/extra/bundebug v1.1.8/go.mod h1:AXl9cPt1j3Yyu+a681xTlDyWoIBL1iSjTjr2SAU5oUY= +github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk= +github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go-micro.dev/v4 v4.8.1 h1:6/3YtUQ+ofkMJh98lC8TkpuMGoFS8kVcj0cnkQhzwDY= +go-micro.dev/v4 v4.8.1/go.mod h1:Ju8HrZ5hQSF+QguZ2QUs9Kbe42MHP1tJa/fpP5g07Cs= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= +golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 h1:ohgcoMbSofXygzo6AD2I1kz3BFmW1QArPYTtwEM3UXc= +golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa h1:VWkrxnAx2C2hirAP+W5ADU7e/+93Yhk//ioKd2XFyDI= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +jochum.dev/jo-micro/router v0.2.3 h1:3wLpYQu2COW4GKaWQqzc6pBMaYUhpC/io+vIiNRajFY= +jochum.dev/jo-micro/router v0.2.3/go.mod h1:2uTL8+2ud2cLzCyKG7T+ymK4MPyfWk8C/aJAJksHCoQ= diff --git a/internal/bun/bun.go b/internal/bun/bun.go new file mode 100644 index 0000000..fd099be --- /dev/null +++ b/internal/bun/bun.go @@ -0,0 +1,118 @@ +package bun + +import ( + "database/sql" + "errors" + "fmt" + "strings" + + "github.com/golang-migrate/migrate" + migratePostgres "github.com/golang-migrate/migrate/database/postgres" + "github.com/jackc/pgx" + "github.com/jackc/pgx/log/logrusadapter" + "github.com/jackc/pgx/stdlib" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/extra/bundebug" + + iLogger "jochum.dev/jo-micro/auth2/internal/logger" + + "github.com/urfave/cli/v2" +) + +var initialized = false +var SQLDB *sql.DB +var Bun *bun.DB + +func Flags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "database-url", + Usage: "bun Database URL", + EnvVars: []string{"DATABASE_URL"}, + }, + &cli.BoolFlag{ + Name: "database-debug", + Usage: "Set it to the debug the database queries", + EnvVars: []string{"DATABASE_DEBUG"}, + DefaultText: "false", + Value: false, + }, + &cli.StringFlag{ + Name: "migrations-table", + Value: "schema_migrations", + Usage: "Table to store migrations info", + EnvVars: []string{"MIGRATIONS_TABLE"}, + }, + &cli.StringFlag{ + Name: "migrations-dir", + Value: "/migrations", + Usage: "Folder which contains migrations", + EnvVars: []string{"MIGRATIONS_DIR"}, + }, + } +} + +func Intialized() bool { + return initialized +} + +func Start(cli *cli.Context) error { + if initialized { + return nil + } + + if strings.HasPrefix(cli.String("database-uri"), "postgres://") { + config, err := pgx.ParseURI(cli.String("database-url")) + if err != nil { + return err + } + + config.PreferSimpleProtocol = true + + if iLogger.Intialized() { + config.Logger = logrusadapter.NewLogger(iLogger.Logrus()) + } + + SQLDB = stdlib.OpenDB(config) + driver, err := migratePostgres.WithInstance(SQLDB, &migratePostgres.Config{MigrationsTable: cli.String("migrations-table")}) + if err != nil { + return err + } + + m, err := migrate.NewWithDatabaseInstance( + fmt.Sprintf("file://%s/postgres", cli.String("migrations-dir")), + "postgres", driver) + if err != nil { + return err + } + if err := m.Up(); err != migrate.ErrNoChange && err != nil { + return err + } + + Bun = bun.NewDB(SQLDB, pgdialect.New()) + if Bun == nil { + return errors.New("failed to create bun") + } + + if cli.Bool("database-debug") { + // Print all queries to stdout. + Bun.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true))) + } + } + + initialized = true + return nil +} + +func Stop() error { + if err := SQLDB.Close(); err != nil { + return err + } + + if err := Bun.Close(); err != nil { + return err + } + + return nil +} diff --git a/internal/config/load.go b/internal/config/load.go new file mode 100644 index 0000000..ea2dd64 --- /dev/null +++ b/internal/config/load.go @@ -0,0 +1,75 @@ +package config + +import ( + "os" + "strings" + + "github.com/go-micro/plugins/v4/config/encoder/toml" + "github.com/go-micro/plugins/v4/config/encoder/yaml" + "github.com/pkg/errors" + "go-micro.dev/v4/config" + "go-micro.dev/v4/config/reader" + "go-micro.dev/v4/config/reader/json" + "go-micro.dev/v4/config/source/env" + "go-micro.dev/v4/config/source/file" + "go-micro.dev/v4/logger" + "jochum.dev/jo-micro/auth2/internal/util" +) + +// Load will load configurations and update it when changed +func Load(cfg interface{}) error { + var configor config.Config + var err error + switch strings.ToLower(os.Getenv("CONFIG_TYPE")) { + case "toml": + filename := "config.toml" + if name := os.Getenv("CONFIG_FILE"); len(name) > 0 { + filename = name + } + configor, err = config.NewConfig( + config.WithSource(file.NewSource(file.WithPath(filename))), + config.WithReader(json.NewReader(reader.WithEncoder(toml.NewEncoder()))), + ) + case "yaml": + filename := "config.yaml" + if name := os.Getenv("CONFIG_FILE"); len(name) > 0 { + filename = name + } + configor, err = config.NewConfig( + config.WithSource(file.NewSource(file.WithPath(filename))), + config.WithReader(json.NewReader(reader.WithEncoder(yaml.NewEncoder()))), + ) + default: + configor, err = config.NewConfig( + config.WithSource(env.NewSource()), + ) + } + if err != nil { + return errors.Wrap(err, "configor.New") + } + if err := configor.Load(); err != nil { + return errors.Wrap(err, "configor.Load") + } + if err := configor.Scan(cfg); err != nil { + return errors.Wrap(err, "configor.Scan") + } + + w, err := configor.Watch() + if err != nil { + return errors.Wrap(err, "configor.Watch") + } + util.GoSafe(func() { + for { + v, err := w.Next() + if err != nil { + logger.Error(err) + return + } + if err := v.Scan(cfg); err != nil { + logger.Error(err) + return + } + } + }) + return nil +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..62c3fd1 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,79 @@ +package logger + +import ( + "fmt" + "os" + "runtime" + + microLogrus "github.com/go-micro/plugins/v4/logger/logrus" + microLogger "go-micro.dev/v4/logger" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +var myLogger *logrus.Logger = nil +var initialized = false + +func Flags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "loglevel", + Value: "info", + Usage: "Logrus log level default 'info', {panic,fatal,error,warn,info,debug,trace} available", + EnvVars: []string{"LOG_LEVEL"}, + }, + } +} + +func Intialized() bool { + return initialized +} + +// caller returns string presentation of log caller which is formatted as +// `/path/to/file.go:line_number`. e.g. `/internal/app/api.go:25` +func caller() func(*runtime.Frame) (function string, file string) { + return func(f *runtime.Frame) (function string, file string) { + return "", fmt.Sprintf("%s:%d", f.File, f.Line) + } +} + +func Start(cli *cli.Context) error { + if initialized { + return nil + } + + lvl, err := logrus.ParseLevel(cli.String("loglevel")) + if err != nil { + return err + } + + myLogger = logrus.New() + myLogger.Out = os.Stdout + myLogger.Level = lvl + + myLogger.SetReportCaller(true) + + myLogger.SetFormatter(&logrus.JSONFormatter{ + CallerPrettyfier: caller(), + FieldMap: logrus.FieldMap{ + logrus.FieldKeyFile: "caller", + }, + }) + + microLogger.DefaultLogger = microLogrus.NewLogger(microLogrus.WithLogger(myLogger)) + + initialized = true + return nil +} + +func Stop() error { + initialized = false + myLogger = nil + + return nil +} + +func Logrus() *logrus.Logger { + return myLogger +} diff --git a/internal/proto/authpb/authpb.pb.go b/internal/proto/authpb/authpb.pb.go new file mode 100644 index 0000000..5e0d8ac --- /dev/null +++ b/internal/proto/authpb/authpb.pb.go @@ -0,0 +1,996 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.5 +// source: authpb.proto + +package authpb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + 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 KeysReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Alg string `protobuf:"bytes,1,opt,name=alg,proto3" json:"alg,omitempty"` + AccessPubKey []byte `protobuf:"bytes,2,opt,name=accessPubKey,proto3" json:"accessPubKey,omitempty"` + RefreshPubKey []byte `protobuf:"bytes,3,opt,name=refreshPubKey,proto3" json:"refreshPubKey,omitempty"` +} + +func (x *KeysReply) Reset() { + *x = KeysReply{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *KeysReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KeysReply) ProtoMessage() {} + +func (x *KeysReply) ProtoReflect() protoreflect.Message { + mi := &file_authpb_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 KeysReply.ProtoReflect.Descriptor instead. +func (*KeysReply) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{0} +} + +func (x *KeysReply) GetAlg() string { + if x != nil { + return x.Alg + } + return "" +} + +func (x *KeysReply) GetAccessPubKey() []byte { + if x != nil { + return x.AccessPubKey + } + return nil +} + +func (x *KeysReply) GetRefreshPubKey() []byte { + if x != nil { + return x.RefreshPubKey + } + return nil +} + +type TokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccessToken string `protobuf:"bytes,1,opt,name=accessToken,proto3" json:"accessToken,omitempty"` +} + +func (x *TokenRequest) Reset() { + *x = TokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenRequest) ProtoMessage() {} + +func (x *TokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_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 TokenRequest.ProtoReflect.Descriptor instead. +func (*TokenRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{1} +} + +func (x *TokenRequest) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Limit uint64 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_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 ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{2} +} + +func (x *ListRequest) GetLimit() uint64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListRequest) GetOffset() uint64 { + if x != nil { + return x.Offset + } + return 0 +} + +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Issuer string `protobuf:"bytes,3,opt,name=issuer,proto3" json:"issuer,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Scopes []string `protobuf:"bytes,5,rep,name=scopes,proto3" json:"scopes,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *User) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*User) ProtoMessage() {} + +func (x *User) ProtoReflect() protoreflect.Message { + mi := &file_authpb_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 User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{3} +} + +func (x *User) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *User) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *User) GetIssuer() string { + if x != nil { + return x.Issuer + } + return "" +} + +func (x *User) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *User) GetScopes() []string { + if x != nil { + return x.Scopes + } + return nil +} + +type UserListReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []*User `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"` + Count uint64 `protobuf:"varint,2,opt,name=count,proto3" json:"count,omitempty"` + Limit uint64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint64 `protobuf:"varint,4,opt,name=offset,proto3" json:"offset,omitempty"` +} + +func (x *UserListReply) Reset() { + *x = UserListReply{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserListReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserListReply) ProtoMessage() {} + +func (x *UserListReply) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[4] + 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 UserListReply.ProtoReflect.Descriptor instead. +func (*UserListReply) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{4} +} + +func (x *UserListReply) GetData() []*User { + if x != nil { + return x.Data + } + return nil +} + +func (x *UserListReply) GetCount() uint64 { + if x != nil { + return x.Count + } + return 0 +} + +func (x *UserListReply) GetLimit() uint64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *UserListReply) GetOffset() uint64 { + if x != nil { + return x.Offset + } + return 0 +} + +type UserIDRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` +} + +func (x *UserIDRequest) Reset() { + *x = UserIDRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserIDRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserIDRequest) ProtoMessage() {} + +func (x *UserIDRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[5] + 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 UserIDRequest.ProtoReflect.Descriptor instead. +func (*UserIDRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{5} +} + +func (x *UserIDRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type UpdateRolesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId string `protobuf:"bytes,1,opt,name=userId,proto3" json:"userId,omitempty"` + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` +} + +func (x *UpdateRolesRequest) Reset() { + *x = UpdateRolesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRolesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRolesRequest) ProtoMessage() {} + +func (x *UpdateRolesRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[6] + 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 UpdateRolesRequest.ProtoReflect.Descriptor instead. +func (*UpdateRolesRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateRolesRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *UpdateRolesRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +type TokenReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AccessToken string `protobuf:"bytes,1,opt,name=accessToken,proto3" json:"accessToken,omitempty"` + AccessTokenExpiresAt int64 `protobuf:"varint,2,opt,name=accessTokenExpiresAt,proto3" json:"accessTokenExpiresAt,omitempty"` + RefreshToken string `protobuf:"bytes,3,opt,name=refreshToken,proto3" json:"refreshToken,omitempty"` + RefreshTokenExpiresAt int64 `protobuf:"varint,4,opt,name=refreshTokenExpiresAt,proto3" json:"refreshTokenExpiresAt,omitempty"` +} + +func (x *TokenReply) Reset() { + *x = TokenReply{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenReply) ProtoMessage() {} + +func (x *TokenReply) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[7] + 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 TokenReply.ProtoReflect.Descriptor instead. +func (*TokenReply) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{7} +} + +func (x *TokenReply) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *TokenReply) GetAccessTokenExpiresAt() int64 { + if x != nil { + return x.AccessTokenExpiresAt + } + return 0 +} + +func (x *TokenReply) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +func (x *TokenReply) GetRefreshTokenExpiresAt() int64 { + if x != nil { + return x.RefreshTokenExpiresAt + } + return 0 +} + +type RegisterRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"` +} + +func (x *RegisterRequest) Reset() { + *x = RegisterRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterRequest) ProtoMessage() {} + +func (x *RegisterRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[8] + 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 RegisterRequest.ProtoReflect.Descriptor instead. +func (*RegisterRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{8} +} + +func (x *RegisterRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *RegisterRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *RegisterRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +type LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginRequest) ProtoMessage() {} + +func (x *LoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[9] + 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 LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{9} +} + +func (x *LoginRequest) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type RefreshTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RefreshToken string `protobuf:"bytes,1,opt,name=refreshToken,proto3" json:"refreshToken,omitempty"` +} + +func (x *RefreshTokenRequest) Reset() { + *x = RefreshTokenRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_authpb_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RefreshTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokenRequest) ProtoMessage() {} + +func (x *RefreshTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_authpb_proto_msgTypes[10] + 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 RefreshTokenRequest.ProtoReflect.Descriptor instead. +func (*RefreshTokenRequest) Descriptor() ([]byte, []int) { + return file_authpb_proto_rawDescGZIP(), []int{10} +} + +func (x *RefreshTokenRequest) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +var File_authpb_proto protoreflect.FileDescriptor + +var file_authpb_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, + 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x67, 0x0a, 0x09, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, + 0x6c, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x72, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x22, 0x30, 0x0a, 0x0c, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3b, + 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0xcf, 0x01, 0x0a, 0x04, + 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, + 0x12, 0x36, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, + 0x1a, 0x3b, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x75, 0x0a, + 0x0d, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, + 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x22, 0x27, 0x0a, 0x0d, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x42, 0x0a, + 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x72, + 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x22, 0xbc, 0x01, 0x0a, 0x0a, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x32, 0x0a, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x14, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x78, 0x70, + 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x34, 0x0a, 0x15, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, + 0x73, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x72, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, + 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x22, 0x46, 0x0a, 0x0c, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x39, 0x0a, 0x13, 0x52, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xc3, 0x03, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x15, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x06, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x12, 0x15, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x06, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, + 0x00, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x17, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, + 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x3c, 0x0a, 0x07, 0x52, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x07, 0x49, 0x6e, 0x73, + 0x70, 0x65, 0x63, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x2e, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x70, 0x62, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x6a, 0x6f, + 0x63, 0x68, 0x75, 0x6d, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x6a, 0x6f, 0x2d, 0x6d, 0x69, 0x63, 0x72, + 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x70, 0x62, 0x3b, 0x61, 0x75, 0x74, + 0x68, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authpb_proto_rawDescOnce sync.Once + file_authpb_proto_rawDescData = file_authpb_proto_rawDesc +) + +func file_authpb_proto_rawDescGZIP() []byte { + file_authpb_proto_rawDescOnce.Do(func() { + file_authpb_proto_rawDescData = protoimpl.X.CompressGZIP(file_authpb_proto_rawDescData) + }) + return file_authpb_proto_rawDescData +} + +var file_authpb_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_authpb_proto_goTypes = []interface{}{ + (*KeysReply)(nil), // 0: authpb.KeysReply + (*TokenRequest)(nil), // 1: authpb.TokenRequest + (*ListRequest)(nil), // 2: authpb.ListRequest + (*User)(nil), // 3: authpb.User + (*UserListReply)(nil), // 4: authpb.UserListReply + (*UserIDRequest)(nil), // 5: authpb.UserIDRequest + (*UpdateRolesRequest)(nil), // 6: authpb.UpdateRolesRequest + (*TokenReply)(nil), // 7: authpb.TokenReply + (*RegisterRequest)(nil), // 8: authpb.RegisterRequest + (*LoginRequest)(nil), // 9: authpb.LoginRequest + (*RefreshTokenRequest)(nil), // 10: authpb.RefreshTokenRequest + nil, // 11: authpb.User.MetadataEntry + (*emptypb.Empty)(nil), // 12: google.protobuf.Empty +} +var file_authpb_proto_depIdxs = []int32{ + 11, // 0: authpb.User.metadata:type_name -> authpb.User.MetadataEntry + 3, // 1: authpb.UserListReply.data:type_name -> authpb.User + 2, // 2: authpb.AuthService.List:input_type -> authpb.ListRequest + 5, // 3: authpb.AuthService.Detail:input_type -> authpb.UserIDRequest + 5, // 4: authpb.AuthService.Delete:input_type -> authpb.UserIDRequest + 6, // 5: authpb.AuthService.UpdateRoles:input_type -> authpb.UpdateRolesRequest + 8, // 6: authpb.AuthService.Register:input_type -> authpb.RegisterRequest + 9, // 7: authpb.AuthService.Login:input_type -> authpb.LoginRequest + 10, // 8: authpb.AuthService.Refresh:input_type -> authpb.RefreshTokenRequest + 1, // 9: authpb.AuthService.Inspect:input_type -> authpb.TokenRequest + 4, // 10: authpb.AuthService.List:output_type -> authpb.UserListReply + 3, // 11: authpb.AuthService.Detail:output_type -> authpb.User + 12, // 12: authpb.AuthService.Delete:output_type -> google.protobuf.Empty + 3, // 13: authpb.AuthService.UpdateRoles:output_type -> authpb.User + 3, // 14: authpb.AuthService.Register:output_type -> authpb.User + 7, // 15: authpb.AuthService.Login:output_type -> authpb.TokenReply + 7, // 16: authpb.AuthService.Refresh:output_type -> authpb.TokenReply + 3, // 17: authpb.AuthService.Inspect:output_type -> authpb.User + 10, // [10:18] is the sub-list for method output_type + 2, // [2:10] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_authpb_proto_init() } +func file_authpb_proto_init() { + if File_authpb_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_authpb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*KeysReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*User); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserListReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserIDRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRolesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TokenReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LoginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_authpb_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RefreshTokenRequest); 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_authpb_proto_rawDesc, + NumEnums: 0, + NumMessages: 12, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_authpb_proto_goTypes, + DependencyIndexes: file_authpb_proto_depIdxs, + MessageInfos: file_authpb_proto_msgTypes, + }.Build() + File_authpb_proto = out.File + file_authpb_proto_rawDesc = nil + file_authpb_proto_goTypes = nil + file_authpb_proto_depIdxs = nil +} diff --git a/internal/proto/authpb/authpb.pb.micro.go b/internal/proto/authpb/authpb.pb.micro.go new file mode 100644 index 0000000..8c6a2c9 --- /dev/null +++ b/internal/proto/authpb/authpb.pb.micro.go @@ -0,0 +1,215 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: authpb.proto + +package authpb + +import ( + fmt "fmt" + proto "google.golang.org/protobuf/proto" + emptypb "google.golang.org/protobuf/types/known/emptypb" + math "math" +) + +import ( + context "context" + api "go-micro.dev/v4/api" + client "go-micro.dev/v4/client" + server "go-micro.dev/v4/server" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Reference imports to suppress errors if they are not otherwise used. +var _ api.Endpoint +var _ context.Context +var _ client.Option +var _ server.Option + +// Api Endpoints for AuthService service + +func NewAuthServiceEndpoints() []*api.Endpoint { + return []*api.Endpoint{} +} + +// Client API for AuthService service + +type AuthService interface { + // * + // @auth AdminAndService + List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*UserListReply, error) + // * + // @auth AuthenticatedUsers + Detail(ctx context.Context, in *UserIDRequest, opts ...client.CallOption) (*User, error) + Delete(ctx context.Context, in *UserIDRequest, opts ...client.CallOption) (*emptypb.Empty, error) + UpdateRoles(ctx context.Context, in *UpdateRolesRequest, opts ...client.CallOption) (*User, error) + Register(ctx context.Context, in *RegisterRequest, opts ...client.CallOption) (*User, error) + Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*TokenReply, error) + Refresh(ctx context.Context, in *RefreshTokenRequest, opts ...client.CallOption) (*TokenReply, error) + Inspect(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*User, error) +} + +type authService struct { + c client.Client + name string +} + +func NewAuthService(name string, c client.Client) AuthService { + return &authService{ + c: c, + name: name, + } +} + +func (c *authService) List(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*UserListReply, error) { + req := c.c.NewRequest(c.name, "AuthService.List", in) + out := new(UserListReply) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Detail(ctx context.Context, in *UserIDRequest, opts ...client.CallOption) (*User, error) { + req := c.c.NewRequest(c.name, "AuthService.Detail", in) + out := new(User) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Delete(ctx context.Context, in *UserIDRequest, opts ...client.CallOption) (*emptypb.Empty, error) { + req := c.c.NewRequest(c.name, "AuthService.Delete", in) + out := new(emptypb.Empty) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) UpdateRoles(ctx context.Context, in *UpdateRolesRequest, opts ...client.CallOption) (*User, error) { + req := c.c.NewRequest(c.name, "AuthService.UpdateRoles", in) + out := new(User) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Register(ctx context.Context, in *RegisterRequest, opts ...client.CallOption) (*User, error) { + req := c.c.NewRequest(c.name, "AuthService.Register", in) + out := new(User) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Login(ctx context.Context, in *LoginRequest, opts ...client.CallOption) (*TokenReply, error) { + req := c.c.NewRequest(c.name, "AuthService.Login", in) + out := new(TokenReply) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Refresh(ctx context.Context, in *RefreshTokenRequest, opts ...client.CallOption) (*TokenReply, error) { + req := c.c.NewRequest(c.name, "AuthService.Refresh", in) + out := new(TokenReply) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authService) Inspect(ctx context.Context, in *TokenRequest, opts ...client.CallOption) (*User, error) { + req := c.c.NewRequest(c.name, "AuthService.Inspect", in) + out := new(User) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for AuthService service + +type AuthServiceHandler interface { + // * + // @auth AdminAndService + List(context.Context, *ListRequest, *UserListReply) error + // * + // @auth AuthenticatedUsers + Detail(context.Context, *UserIDRequest, *User) error + Delete(context.Context, *UserIDRequest, *emptypb.Empty) error + UpdateRoles(context.Context, *UpdateRolesRequest, *User) error + Register(context.Context, *RegisterRequest, *User) error + Login(context.Context, *LoginRequest, *TokenReply) error + Refresh(context.Context, *RefreshTokenRequest, *TokenReply) error + Inspect(context.Context, *TokenRequest, *User) error +} + +func RegisterAuthServiceHandler(s server.Server, hdlr AuthServiceHandler, opts ...server.HandlerOption) error { + type authService interface { + List(ctx context.Context, in *ListRequest, out *UserListReply) error + Detail(ctx context.Context, in *UserIDRequest, out *User) error + Delete(ctx context.Context, in *UserIDRequest, out *emptypb.Empty) error + UpdateRoles(ctx context.Context, in *UpdateRolesRequest, out *User) error + Register(ctx context.Context, in *RegisterRequest, out *User) error + Login(ctx context.Context, in *LoginRequest, out *TokenReply) error + Refresh(ctx context.Context, in *RefreshTokenRequest, out *TokenReply) error + Inspect(ctx context.Context, in *TokenRequest, out *User) error + } + type AuthService struct { + authService + } + h := &authServiceHandler{hdlr} + return s.Handle(s.NewHandler(&AuthService{h}, opts...)) +} + +type authServiceHandler struct { + AuthServiceHandler +} + +func (h *authServiceHandler) List(ctx context.Context, in *ListRequest, out *UserListReply) error { + return h.AuthServiceHandler.List(ctx, in, out) +} + +func (h *authServiceHandler) Detail(ctx context.Context, in *UserIDRequest, out *User) error { + return h.AuthServiceHandler.Detail(ctx, in, out) +} + +func (h *authServiceHandler) Delete(ctx context.Context, in *UserIDRequest, out *emptypb.Empty) error { + return h.AuthServiceHandler.Delete(ctx, in, out) +} + +func (h *authServiceHandler) UpdateRoles(ctx context.Context, in *UpdateRolesRequest, out *User) error { + return h.AuthServiceHandler.UpdateRoles(ctx, in, out) +} + +func (h *authServiceHandler) Register(ctx context.Context, in *RegisterRequest, out *User) error { + return h.AuthServiceHandler.Register(ctx, in, out) +} + +func (h *authServiceHandler) Login(ctx context.Context, in *LoginRequest, out *TokenReply) error { + return h.AuthServiceHandler.Login(ctx, in, out) +} + +func (h *authServiceHandler) Refresh(ctx context.Context, in *RefreshTokenRequest, out *TokenReply) error { + return h.AuthServiceHandler.Refresh(ctx, in, out) +} + +func (h *authServiceHandler) Inspect(ctx context.Context, in *TokenRequest, out *User) error { + return h.AuthServiceHandler.Inspect(ctx, in, out) +} diff --git a/internal/proto/authpb/authpb.proto b/internal/proto/authpb/authpb.proto new file mode 100644 index 0000000..859daa1 --- /dev/null +++ b/internal/proto/authpb/authpb.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +package authpb; + +option go_package = "jochum.dev/jo-micro/auth2/internal/proto/authpb;authpb"; + +import "google/protobuf/empty.proto"; + +service AuthService { + /** + * @auth AdminAndService + */ + rpc List(ListRequest) returns (UserListReply) {} + + /** + * @auth AuthenticatedUsers + */ + rpc Detail(UserIDRequest) returns (User) {} + rpc Delete(UserIDRequest) returns (google.protobuf.Empty) {} + rpc UpdateRoles(UpdateRolesRequest) returns (User) {} + + rpc Register(RegisterRequest) returns (User) {} + rpc Login(LoginRequest) returns (TokenReply) {} + rpc Refresh(RefreshTokenRequest) returns (TokenReply) {} + + rpc Inspect (TokenRequest) returns (User) {} +} + +message KeysReply { + string alg = 1; + bytes accessPubKey = 2; + bytes refreshPubKey = 3; +} + +message TokenRequest { + string accessToken = 1; +} + +message ListRequest { + uint64 limit = 1; + uint64 offset = 2; +} + +message User { + string id = 1; + string type = 2; + string issuer = 3; + map metadata = 4; + repeated string scopes = 5; +} + +message UserListReply { + repeated User data = 1; + uint64 count = 2; + uint64 limit = 3; + uint64 offset = 4; +} + +message UserIDRequest { + string userId = 1; +} + +message UpdateRolesRequest { + string userId = 1; + repeated string roles = 2; +} + +message TokenReply { + string accessToken = 1; + int64 accessTokenExpiresAt = 2; + string refreshToken = 3; + int64 refreshTokenExpiresAt = 4; +} + +message RegisterRequest { + string username = 1; + string password = 2; + string email = 3; +} + +message LoginRequest { + string username = 1; + string password = 2; +} + +message RefreshTokenRequest { + string refreshToken = 1; +} diff --git a/internal/util/goroutine.go b/internal/util/goroutine.go new file mode 100644 index 0000000..eba459a --- /dev/null +++ b/internal/util/goroutine.go @@ -0,0 +1,22 @@ +package util + +import ( + "runtime/debug" + + "go-micro.dev/v4/logger" +) + +// GoSafe will run func in goroutine safely, avoid crash from unexpected panic +func GoSafe(fn func()) { + if fn == nil { + return + } + go func() { + defer func() { + if e := recover(); e != nil { + logger.Errorf("[panic]%v\n%s", e, debug.Stack()) + } + }() + fn() + }() +} diff --git a/internal/util/retry.go b/internal/util/retry.go new file mode 100644 index 0000000..752ab7b --- /dev/null +++ b/internal/util/retry.go @@ -0,0 +1,46 @@ +package util + +import ( + "fmt" + + "github.com/avast/retry-go" + "go-micro.dev/v4" +) + +func ServiceRetryGet(service micro.Service, svcName string, attempts uint) (string, error) { + r := service.Options().Registry + + var ( + hostAndPort string + ) + + err := retry.Do( + func() error { + services, err := r.GetService(svcName) + if err == nil { + for _, s := range services { + for _, n := range s.Nodes { + hostAndPort = n.Address + break + } + if hostAndPort != "" { + break + } + } + } + + if hostAndPort == "" { + return fmt.Errorf("Service %v not found", svcName) + } + + return nil + }, + retry.Attempts(attempts), + ) + + if err != nil { + return "", err + } + + return hostAndPort, nil +} diff --git a/internal/util/serviceregistry.go b/internal/util/serviceregistry.go new file mode 100644 index 0000000..98fa45d --- /dev/null +++ b/internal/util/serviceregistry.go @@ -0,0 +1,73 @@ +package util + +import ( + "go-micro.dev/v4" + "go-micro.dev/v4/registry" +) + +type ServiceListResult map[*registry.Service][]*registry.Endpoint + +type WrappedEndpoint struct { + Pre string + Handler string +} + +func Endpoints(service micro.Service, regService *registry.Service) ([]*registry.Endpoint, error) { + if len(regService.Endpoints) > 0 { + eps := append([]*registry.Endpoint{}, regService.Endpoints...) + return eps, nil + } + // lookup the endpoints otherwise + newServices, err := service.Options().Registry.GetService(regService.Name) + if err != nil { + return []*registry.Endpoint{}, err + } + if len(newServices) == 0 { + return []*registry.Endpoint{}, err + } + + eps := []*registry.Endpoint{} + for _, s := range newServices { + eps = append(eps, s.Endpoints...) + } + + return eps, nil +} + +func ListEndpoints(service micro.Service) (ServiceListResult, error) { + services, err := service.Options().Registry.ListServices() + if err != nil { + return nil, err + } + + endpoints := make(ServiceListResult) + for _, srv := range services { + eps, err := Endpoints(service, srv) + if err != nil { + continue + } + + endpoints[srv] = eps + } + + return endpoints, nil +} + +func FindByEndpoint(service micro.Service, endpoint interface{}) ([]*registry.Service, error) { + services, err := ListEndpoints(service) + if err != nil { + return []*registry.Service{}, err + } + + strEndpoint := ReflectFunctionName(endpoint) + result := []*registry.Service{} + for s, eps := range services { + for _, ep := range eps { + if ep.Name == strEndpoint { + result = append(result, s) + } + } + } + + return result, nil +} diff --git a/internal/util/token.go b/internal/util/token.go new file mode 100644 index 0000000..44333b3 --- /dev/null +++ b/internal/util/token.go @@ -0,0 +1,31 @@ +package util + +import ( + "errors" + "strings" +) + +type TokenFormat int32 + +const TokenFormatUnknown TokenFormat = 0 +const TokenFormatBearer TokenFormat = 1 +const TokenFormatToken TokenFormat = 2 + +func ExtractToken(hdr string) (string, TokenFormat, error) { + if hdr == "" { + return "", TokenFormatUnknown, errors.New("no authorization header") + } + + th := strings.Split(hdr, " ") + if len(th) != 2 { + return "", TokenFormatUnknown, errors.New("incomplete authorization header") + } + + if strings.ToLower(th[0]) == "bearer" { + return th[1], TokenFormatBearer, nil + } else if strings.ToLower(th[0]) == "token" { + return th[1], TokenFormatToken, nil + } + + return "", TokenFormatUnknown, errors.New("unknow token format") +} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..9e84436 --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,22 @@ +package util + +import ( + "reflect" + "runtime" + "strings" +) + +// ReflectFunctionName Guess Struct.Method from the given Function +func ReflectFunctionName(i interface{}) string { + switch v := i.(type) { + case string: + return v + default: + path := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() + // LTrim until the first / + path = path[strings.LastIndex(path, "/")+1:] + + // Return after the next point + return path[strings.Index(path, ".")+1:] + } +} diff --git a/noop.go b/noop.go new file mode 100644 index 0000000..097fa58 --- /dev/null +++ b/noop.go @@ -0,0 +1,115 @@ +package auth + +import ( + "context" + "net/http" + + "github.com/google/uuid" + "github.com/urfave/cli/v2" + "go-micro.dev/v4" + "go-micro.dev/v4/server" +) + +func init() { + ClientAuthRegistry().Register(newNoopClientPlugin()) + ServiceAuthRegistry().Register(newNoopServicePlugin()) + RouterAuthRegistry().Register(newNoopRouterPlugin()) +} + +func newNoopClientPlugin() ClientPlugin { + return new(noopClientPlugin) +} + +type noopClientPlugin struct{} + +func (p *noopClientPlugin) String() string { + return "noop" +} + +func (p *noopClientPlugin) Flags() []cli.Flag { + return []cli.Flag{} +} + +func (p *noopClientPlugin) Init(cli *cli.Context, service micro.Service) error { + return nil +} + +func (p *noopClientPlugin) Stop() error { + return nil +} + +func (p *noopClientPlugin) Health(ctx context.Context) (string, error) { + return "All fine", nil +} + +func (p *noopClientPlugin) Inspect(ctx context.Context) (*User, error) { + return &User{Id: uuid.New().String(), Issuer: p.String()}, nil +} + +func (p *noopClientPlugin) Wrapper() server.HandlerWrapper { + return func(h server.HandlerFunc) server.HandlerFunc { + return func(ctx context.Context, req server.Request, rsp interface{}) error { + return h(ctx, req, rsp) + } + } +} + +func newNoopServicePlugin() ServerPlugin { + return new(noopServicePlugin) +} + +type noopServicePlugin struct{} + +func (p *noopServicePlugin) String() string { + return "noop" +} + +func (p *noopServicePlugin) Flags() []cli.Flag { + return []cli.Flag{} +} + +func (p *noopServicePlugin) Init(cli *cli.Context, service micro.Service) error { + return nil +} + +func (p *noopServicePlugin) Stop() error { + return nil +} + +func (p *noopServicePlugin) Health(ctx context.Context) (string, error) { + return "All fine", nil +} + +func newNoopRouterPlugin() RouterPlugin { + return new(noopRouterPlugin) +} + +type noopRouterPlugin struct{} + +func (p *noopRouterPlugin) String() string { + return "noop" +} + +func (p *noopRouterPlugin) Flags() []cli.Flag { + return []cli.Flag{} +} + +func (p *noopRouterPlugin) Init(cli *cli.Context, service micro.Service) error { + return nil +} + +func (p *noopRouterPlugin) Stop() error { + return nil +} + +func (p *noopRouterPlugin) Health(ctx context.Context) (string, error) { + return "All fine", nil +} + +func (p *noopRouterPlugin) Inspect(r *http.Request) (*User, error) { + return &User{Id: uuid.New().String(), Issuer: p.String()}, nil +} + +func (p *noopRouterPlugin) ForwardContext(r *http.Request, ctx context.Context) (context.Context, error) { + return ctx, nil +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..f5e25b8 --- /dev/null +++ b/plugin.go @@ -0,0 +1,63 @@ +package auth + +import ( + "context" + "net/http" + + "github.com/urfave/cli/v2" + "go-micro.dev/v4" + "go-micro.dev/v4/server" +) + +type User struct { + Id string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Issuer string `json:"issuer,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` + Scopes []string `json:"scopes,omitempty"` + Roles []string `json:"roles,omitempty"` +} + +type registryFuncs interface { + // String returns the name of the plugin + String() string + + // Flags returns a list of cli.Flag's for micro.Service + Flags() []cli.Flag + + // Init should be executed in micro.Init + Init(cli *cli.Context, service micro.Service) error + + // Stop should be executed after service.Run() + Stop() error + + // Health returns the health of the plugin + Health(ctx context.Context) (string, error) +} + +// ClientPlugin is for services that act as client's behind GinRouter +type ClientPlugin interface { + registryFuncs + + // Inspect a context + Inspect(ctx context.Context) (*User, error) + + // Wrapper returns the Auth Wrapper for your service + Wrapper() server.HandlerWrapper +} + +// ServerPlugin is for authservers +type ServerPlugin interface { + registryFuncs +} + +// RouterPlugin is for routers that forward the token or do other stuff required by ClientPlugin +type RouterPlugin interface { + registryFuncs + + // Inspect a http.Request + Inspect(r *http.Request) (*User, error) + + // ForwardContext should forward all required informations from http.Request to the resulting context. + ForwardContext(r *http.Request, ctx context.Context) (context.Context, error) +} diff --git a/plugins/client/jwt/jwt.go b/plugins/client/jwt/jwt.go new file mode 100644 index 0000000..b7e7d27 --- /dev/null +++ b/plugins/client/jwt/jwt.go @@ -0,0 +1,131 @@ +package jwt + +import ( + "context" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + + "github.com/golang-jwt/jwt/v4" + "github.com/urfave/cli/v2" + "go-micro.dev/v4" + "go-micro.dev/v4/metadata" + "go-micro.dev/v4/server" + "jochum.dev/jo-micro/auth2" + "jochum.dev/jo-micro/auth2/internal/util" +) + +type jWTClaims struct { + *jwt.StandardClaims + Type string `json:"type,omitempty"` + Roles []string `json:"roles,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +func init() { + auth.ClientAuthRegistry().Register(newJWTPlugin()) +} + +func newJWTPlugin() auth.ClientPlugin { + return new(jwtPlugin) +} + +type jwtPlugin struct { + pubKey any +} + +func (p *jwtPlugin) String() string { + return "jwt" +} + +func (p *jwtPlugin) Flags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "auth2_jwt_pub_key", + Usage: "Public key PEM base64 encoded", + EnvVars: []string{"MICRO_AUTH2_JWT_PUB_KEY"}, + }, + } +} + +func (p *jwtPlugin) Init(cli *cli.Context, service micro.Service) error { + if len(cli.String("auth2_jwt_pub_key")) < 1 { + return errors.New("you must provide micro-auth-jwt-pub-key") + } + aPub, err := base64.StdEncoding.DecodeString(cli.String("micro-auth-jwt-pub-key")) + if err != nil { + return err + } + + block, _ := pem.Decode(aPub) + if block == nil { + return errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + + p.pubKey = pub + + return nil +} + +func (p *jwtPlugin) Stop() error { + return nil +} + +func (p *jwtPlugin) Health(ctx context.Context) (string, error) { + return "All fine", nil +} + +func (p *jwtPlugin) Inspect(ctx context.Context) (*auth.User, error) { + md, ok := metadata.FromContext(ctx) + if !ok { + return nil, errors.New("failed to extract metadata from context") + } + + authH, ok := md.Get("Authorization") + if !ok { + return nil, errors.New("failed to get Authorization header from context") + } + + aTokenString, _, err := util.ExtractToken(authH) + if err != nil { + return nil, err + } + + claims := jWTClaims{} + _, err = jwt.ParseWithClaims(aTokenString, &claims, func(token *jwt.Token) (interface{}, error) { + return p.pubKey, nil + }) + if err != nil { + return nil, err + } + + cMD := map[string]string{ + "Audience": claims.Audience, + "ExpiresAt": fmt.Sprintf("%d", claims.ExpiresAt), + "IssuedAt": fmt.Sprintf("%d", claims.IssuedAt), + "NotBefore": fmt.Sprintf("%d", claims.NotBefore), + "Subject": claims.Subject, + } + + return &auth.User{Id: claims.Id, Type: claims.Type, Issuer: claims.Issuer, Metadata: cMD, Scopes: claims.Scopes, Roles: claims.Roles}, nil +} + +func (p *jwtPlugin) Wrapper() server.HandlerWrapper { + return func(h server.HandlerFunc) server.HandlerFunc { + return func(ctx context.Context, req server.Request, rsp interface{}) error { + _, err := p.Inspect(ctx) + if err != nil { + return err + } + + return h(ctx, req, rsp) + } + } +} diff --git a/plugins/router/jwt/jwt.go b/plugins/router/jwt/jwt.go new file mode 100644 index 0000000..dc8fe20 --- /dev/null +++ b/plugins/router/jwt/jwt.go @@ -0,0 +1,129 @@ +package jwt + +import ( + "context" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "net/http" + + "github.com/golang-jwt/jwt/v4" + "github.com/urfave/cli/v2" + "go-micro.dev/v4" + "go-micro.dev/v4/metadata" + "jochum.dev/jo-micro/auth2" + "jochum.dev/jo-micro/auth2/internal/util" +) + +type jWTClaims struct { + *jwt.StandardClaims + Type string `json:"type,omitempty"` + Roles []string `json:"roles,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +func init() { + auth.RouterAuthRegistry().Register(newJWTPlugin()) +} + +func newJWTPlugin() auth.RouterPlugin { + return new(jwtPlugin) +} + +type jwtPlugin struct { + pubKey any +} + +func (p *jwtPlugin) String() string { + return "jwt" +} + +func (p *jwtPlugin) Flags() []cli.Flag { + return []cli.Flag{ + &cli.StringFlag{ + Name: "auth2_jwt_pub_key", + Usage: "Public key PEM base64 encoded", + EnvVars: []string{"MICRO_AUTH2_JWT_PUB_KEY"}, + }, + } +} + +func (p *jwtPlugin) Init(cli *cli.Context, service micro.Service) error { + if len(cli.String("auth2_jwt_pub_key")) < 1 { + return errors.New("you must provide micro-auth-jwt-pub-key") + } + aPub, err := base64.StdEncoding.DecodeString(cli.String("micro-auth-jwt-pub-key")) + if err != nil { + return err + } + + block, _ := pem.Decode(aPub) + if block == nil { + return errors.New("failed to parse PEM block containing the key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + + p.pubKey = pub + + return nil +} + +func (p *jwtPlugin) Stop() error { + return nil +} + +func (p *jwtPlugin) Health(ctx context.Context) (string, error) { + return "All fine", nil +} + +func (p *jwtPlugin) Inspect(r *http.Request) (*auth.User, error) { + if h := r.Header.Get("Authorization"); len(h) > 0 { + return nil, errors.New("failed to get Authorization header from context") + } + + aTokenString, _, err := util.ExtractToken(r.Header.Get("Authorization")) + if err != nil { + return nil, err + } + + claims := jWTClaims{} + _, err = jwt.ParseWithClaims(aTokenString, &claims, func(token *jwt.Token) (interface{}, error) { + return p.pubKey, nil + }) + if err != nil { + return nil, err + } + + cMD := map[string]string{ + "Audience": claims.Audience, + "ExpiresAt": fmt.Sprintf("%d", claims.ExpiresAt), + "IssuedAt": fmt.Sprintf("%d", claims.IssuedAt), + "NotBefore": fmt.Sprintf("%d", claims.NotBefore), + "Subject": claims.Subject, + } + + return &auth.User{Id: claims.Id, Type: claims.Type, Issuer: claims.Issuer, Metadata: cMD, Scopes: claims.Scopes, Roles: claims.Roles}, nil +} + +func (p *jwtPlugin) ForwardContext(r *http.Request, ctx context.Context) (context.Context, error) { + _, err := p.Inspect(r) + if err != nil { + return ctx, err + } + + md := metadata.Metadata{ + "Authorization": r.Header.Get("Authorization"), + } + + if v := r.Header.Get("X-Forwarded-For"); len(v) > 0 { + md["X-Fowarded-For"] = v + } + + return metadata.MergeContext(ctx, md, true), nil +} diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..aa74a8a --- /dev/null +++ b/registry.go @@ -0,0 +1,90 @@ +package auth + +import ( + "context" + "fmt" + "strings" + + "github.com/urfave/cli/v2" + "go-micro.dev/v4" +) + +var car = &AuthRegistry[ClientPlugin]{kind: "client", plugins: make(map[string]ClientPlugin)} +var sar = &AuthRegistry[ServerPlugin]{kind: "service", plugins: make(map[string]ServerPlugin)} +var rar = &AuthRegistry[RouterPlugin]{kind: "router", plugins: make(map[string]RouterPlugin)} + +func ClientAuthRegistry() *AuthRegistry[ClientPlugin] { + return car +} + +func ServiceAuthRegistry() *AuthRegistry[ServerPlugin] { + return sar +} + +func RouterAuthRegistry() *AuthRegistry[RouterPlugin] { + return rar +} + +type AuthRegistry[T any] struct { + kind string + plugin T + plugins map[string]T +} + +// Register registers a plugin within AuthRegistry +func (r *AuthRegistry[T]) Register(plugin T) { + if s, ok := any(plugin).(registryFuncs); ok { + r.plugins[s.String()] = plugin + } +} + +// Flags returns a list of cli.Flag's for micro.Service +func (r *AuthRegistry[T]) Flags() []cli.Flag { + flags := []cli.Flag{ + &cli.StringFlag{ + Name: fmt.Sprintf("auth2_%s", r.kind), + Usage: fmt.Sprintf("Auth %s Plugin to use", r.kind), + EnvVars: []string{fmt.Sprintf("MICRO_AUTH2_%s", strings.ToUpper(r.kind))}, + Value: "noop", + }, + } + for _, p := range r.plugins { + if p2, ok := any(p).(registryFuncs); ok { + flags = append(flags, p2.Flags()...) + } + } + + return flags +} + +// Plugin returns the current active Plugin +func (r *AuthRegistry[T]) Plugin() T { + return r.plugin +} + +// Init should be executed in micro.Init +func (r *AuthRegistry[T]) Init(cli *cli.Context, service micro.Service) error { + plugin := cli.String(fmt.Sprintf("auth2_%s", r.kind)) + + m, ok := r.plugins[plugin] + if !ok { + return fmt.Errorf("unknown MICRO_AUTH2_%s plugin '%s'", strings.ToUpper(r.kind), plugin) + } + + r.plugin = m + + m2, _ := any(m).(registryFuncs) + return m2.Init(cli, service) +} + +// Stop should be executed after service.Run() +func (r *AuthRegistry[T]) Stop() error { + m, _ := any(r.plugin).(registryFuncs) + return m.Stop() +} + +// Health returns the health of the plugin +func (r *AuthRegistry[T]) Health(ctx context.Context) (string, error) { + m, _ := any(r.plugin).(registryFuncs) + return m.Health(ctx) +}