diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42832e70..53dd1532 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,3 +27,42 @@ jobs: - name: Run tests run: make test + + proto: + name: Proto lint + breaking + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + # buf breaking needs main's history to diff against. + fetch-depth: 0 + + # buf breaking can only diff against main once main actually carries + # the proto module; before this PR series merges, main has no proto/ + # and `buf breaking` fails with "had no .proto files". + - name: Check base branch has protos + id: base + run: | + if git ls-tree -d origin/main proto | grep -q proto; then + echo "has_proto=true" >> "$GITHUB_OUTPUT" + else + echo "has_proto=false" >> "$GITHUB_OUTPUT" + fi + + - uses: bufbuild/buf-action@v1 + with: + input: proto + lint: true + # `format: true` makes the action run `buf format -d --exit-code`, + # failing the job on any unformatted .proto. Catches drift before + # generated code can diverge. + format: true + # Only run breaking on PRs (push to main has nothing to diff + # against) and only once main carries the proto module. + breaking: ${{ github.event_name == 'pull_request' && steps.base.outputs.has_proto == 'true' }} + breaking_against: 'https://github.com/${{ github.repository }}.git#branch=main,subdir=proto' + # The action's PR comment needs permissions the default + # GITHUB_TOKEN of this job lacks ("Resource not accessible by + # integration"); skip it. + pr_comment: false diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9a3982af..2f84e181 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -36,10 +36,26 @@ jobs: - name: Run tests run: make test + smoke: + name: Release smoke (GraphQL + REST + gRPC + MCP) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + # Black-box end-to-end pass over every public API surface against the + # freshly built binary (see internal/e2e/smoke_test.go). + - name: Run smoke tests + run: make smoke + build: name: Build and push Docker image runs-on: ubuntu-latest - needs: test + needs: [test, smoke] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..eb71d1d3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,32 @@ +# golangci-lint v2 configuration. +# Run via `make lint-go` (or `golangci-lint run ./...`). +version: "2" + +run: + # Build tags used by the test/storage matrix so all files type-check. + tests: true + +linters: + # Default linter set (errcheck, govet, ineffassign, staticcheck, unused). + default: standard + settings: + staticcheck: + checks: + - all + # QF1008 wants embedded-field selectors collapsed (p.Config.X -> p.X); + # the codebase deliberately keeps the explicit p.Config.X form. + - -QF1008 + exclusions: + # Generated protobuf/gateway/openapi code is owned by buf, not us. + generated: lax + paths: + - gen/ + - ".*\\.pb\\.go$" + - ".*\\.pb\\.gw\\.go$" + +formatters: + enable: + - gofmt + exclusions: + paths: + - gen/ diff --git a/Makefile b/Makefile index 09767464..aff1b10c 100644 --- a/Makefile +++ b/Makefile @@ -92,6 +92,14 @@ dev: test: go clean --testcache && TEST_DBS="sqlite" $(GO_TEST_ALL) +# Release smoke tests: build the real binary and exercise every public API +# surface (GraphQL, REST, gRPC, MCP) end to end, including an authenticated +# FGA decision on each. Gated behind the `smoke` build tag so regular test +# runs skip them. CI runs this on every release. +.PHONY: smoke +smoke: + go test -tags smoke -count=1 -v -timeout 5m ./internal/e2e/ + test-postgres: test-cleanup-postgres docker run -d --name authorizer_postgres -p 5434:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres postgres sleep 3 @@ -174,3 +182,71 @@ generate-graphql: generate-db-template: cp -rf internal/storage/db/provider_template internal/storage/db/${dbname} find internal/storage/db/${dbname} -type f -exec sed -i -e 's/provider_template/${dbname}/g' {} \; + +# ---------------------------------------------------------------------------- +# Protobuf (Phase 0+): public-API source of truth under ./proto. +# `buf` is installed on demand into $(GOBIN) if missing. +# ---------------------------------------------------------------------------- +BUF ?= $(shell command -v buf 2>/dev/null) +BUF_VERSION ?= v1.47.2 + +.PHONY: proto-tools proto-lint proto-breaking proto-gen + +proto-tools: + @if [ -z "$(BUF)" ]; then \ + echo "Installing buf $(BUF_VERSION) via go install"; \ + go install github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION); \ + fi + +proto-lint: proto-tools + cd proto && buf lint + +# Compare the working tree's proto against origin/main; fails on breaking changes. +# Override BUF_BREAKING_AGAINST for local runs (e.g. "main" or a SHA). +BUF_BREAKING_AGAINST ?= .git#branch=origin/main,subdir=proto +proto-breaking: proto-tools + cd proto && buf breaking --against '../$(BUF_BREAKING_AGAINST)' + +proto-gen: proto-tools + cd proto && buf dep update && buf generate + +# ---------------------------------------------------------------------------- +# Formatting & linting (Go + TypeScript). `make fmt` before committing, +# `make lint` in CI. golangci-lint is installed on demand if missing. +# ---------------------------------------------------------------------------- +GOLANGCI_LINT ?= $(shell command -v golangci-lint 2>/dev/null) +GOLANGCI_LINT_VERSION ?= v2.11.4 + +.PHONY: fmt fmt-go fmt-ts lint lint-go lint-ts lint-tools + +# Format everything. +fmt: fmt-go fmt-ts + +# gofmt -s over all hand-written Go sources (generated protobuf output under +# gen/ is excluded — it is owned by buf). +fmt-go: + @gofmt -s -w $(shell find . -type f -name '*.go' -not -path './gen/*') + +# Prettier over both web apps via their configured format scripts. +fmt-ts: + cd web/app && npm run format + cd web/dashboard && npm run format + +# Lint everything. +lint: lint-go lint-ts + +lint-tools: + @if [ -z "$(GOLANGCI_LINT)" ]; then \ + echo "Installing golangci-lint $(GOLANGCI_LINT_VERSION)"; \ + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION); \ + fi + +# golangci-lint over the module. Generated code under gen/ is excluded via +# .golangci.yml. +lint-go: lint-tools + golangci-lint run ./... + +# Prettier in --check mode: fails (non-zero) if any web source is unformatted. +lint-ts: + cd web/app && npx prettier --check 'src/**/*.(ts|tsx|js|jsx)' + cd web/dashboard && npx prettier --check 'src/**/*.(ts|tsx|js|jsx)' diff --git a/cmd/fga_engine.go b/cmd/fga_engine.go new file mode 100644 index 00000000..c32451d6 --- /dev/null +++ b/cmd/fga_engine.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "strings" + + "github.com/rs/zerolog" + + "github.com/authorizerdev/authorizer/internal/authorization/engine" + fgaengine "github.com/authorizerdev/authorizer/internal/authorization/engine/openfga" + "github.com/authorizerdev/authorizer/internal/config" +) + +// initAuthzEngine initializes the embedded OpenFGA authorization engine from +// the --fga-store / --fga-store-url config, shared by the server (root) and +// the MCP subcommand. OpenFGA migrations run on boot for SQL stores +// (idempotent); the in-memory store needs none. +// +// Engine-init failure is deliberately NON-fatal: FGA is an optional +// subsystem, so a failure here (e.g. the DB user lacks DDL rights for the +// OpenFGA tables) logs loudly and returns a nil engine — fga_* and the +// permission APIs fail closed while core authentication keeps serving. +// +// The returned cleanup func is always non-nil and safe to defer; it closes +// the engine when one was created. +func initAuthzEngine(cfg *config.Config, log *zerolog.Logger) (engine.AuthorizationEngine, func()) { + cleanup := func() {} + fgaStore, fgaStoreURL, fgaEnabled := cfg.FGAStoreConfig() + if !fgaEnabled { + return nil, cleanup + } + runMigrations := !strings.EqualFold(fgaStore, fgaengine.StoreMemory) + fgaEngine, err := fgaengine.New( + &fgaengine.Config{ + Store: fgaStore, + StoreURL: fgaStoreURL, + StoreName: cfg.OrganizationName, + RunMigrations: runMigrations, + }, + &fgaengine.Dependencies{Log: log}, + ) + if err != nil { + log.Error().Err(err). + Str("fga_store", fgaStore). + Msg("failed to initialize OpenFGA authorization engine; fine-grained authorization is DISABLED (fail-closed) — core auth continues") + return nil, cleanup + } + if closer, ok := fgaEngine.(interface{ Close() }); ok { + cleanup = closer.Close + } + log.Info(). + Str("fga_store", fgaStore). + Bool("reused_main_db", strings.TrimSpace(cfg.FGAStore) == ""). + Msg("OpenFGA authorization engine initialized (embedded)") + return fgaEngine, cleanup +} diff --git a/cmd/mcp.go b/cmd/mcp.go new file mode 100644 index 00000000..f4be1291 --- /dev/null +++ b/cmd/mcp.go @@ -0,0 +1,181 @@ +package cmd + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" + + "github.com/authorizerdev/authorizer/internal/audit" + "github.com/authorizerdev/authorizer/internal/constants" + "github.com/authorizerdev/authorizer/internal/email" + "github.com/authorizerdev/authorizer/internal/events" + "github.com/authorizerdev/authorizer/internal/grpcsrv" + "github.com/authorizerdev/authorizer/internal/mcp" + "github.com/authorizerdev/authorizer/internal/memory_store" + "github.com/authorizerdev/authorizer/internal/service" + "github.com/authorizerdev/authorizer/internal/sms" + "github.com/authorizerdev/authorizer/internal/storage" + "github.com/authorizerdev/authorizer/internal/token" +) + +// mcpArgs are the MCP-subcommand-only flags. The root command's flags +// (--database-type, --client-id, --jwt-secret, ...) are inherited by the +// subcommand automatically since they live on RootCmd. +var mcpArgs struct { + // bearer is propagated as `Authorization: Bearer ` on every + // outgoing gRPC call. Without it the MCP server runs anonymously — + // fine for the `meta` tool (public) but identity-bearing tools + // (`profile`, `check_permissions`, `list_permissions`) won't have a + // caller to attribute to. + bearer string + // authorizerURL is the public URL of the Authorizer instance that + // minted the bearer token; stamped as `x-authorizer-url` so JWT issuer + // validation passes for identity-bearing tools. + authorizerURL string +} + +// mcpCmd serves Authorizer's MCP surface over stdio. Designed to be wired +// into Claude Code or any other MCP host via: +// +// claude mcp add authorizer -- /path/to/authorizer mcp --client-id=... \ +// --database-type=sqlite --database-url=auth.db --mcp-bearer=$TOKEN +// +// Which tools are exposed is declared at the proto layer via the +// `(authorizer.common.v1.mcp_tool).exposed` option; the MCP server discovers +// them at startup. +// +// Transport: STDIO ONLY. The MCP server has no auth/rate-limit interceptors +// of its own — the security model relies on the OS-level trust boundary of +// the subprocess. See internal/mcp/server.go's Server type comment. +var mcpCmd = &cobra.Command{ + Use: "mcp", + Short: "Serve Authorizer's MCP tool surface over stdio", + Long: "Exposes a subset of Authorizer's gRPC methods (those marked " + + "(authorizer.common.v1.mcp_tool).exposed=true in proto) as MCP " + + "tools, suitable for use with Claude Code or any MCP-compatible " + + "host. Stdio is the only supported transport.", + Run: runMCP, +} + +func init() { + mcpCmd.Flags().StringVar(&mcpArgs.bearer, "mcp-bearer", "", + "Bearer token to attach to every outgoing gRPC call (carries the "+ + "user identity for tools like Profile / Permissions / Session). "+ + "When unset the MCP server runs anonymously; public tools (Meta) "+ + "still work but identity-bearing tools will fail authn.") + mcpCmd.Flags().StringVar(&mcpArgs.authorizerURL, "mcp-authorizer-url", "", + "Public URL of the Authorizer instance that issued --mcp-bearer "+ + "(e.g. https://auth.example.com). Required with --mcp-bearer: "+ + "JWT issuer validation compares the token's iss claim against "+ + "this value.") + RootCmd.AddCommand(mcpCmd) +} + +func runMCP(_ *cobra.Command, _ []string) { + // MCP stdio mode: stderr-only logging so it doesn't interleave with the + // JSON-RPC framing on stdout. + log := zerolog.New(os.Stderr).With().Timestamp().Logger() + + // Wire all subsystems an MCP-exposed tool might need. As more ops + // migrate into internal/service, this list stays the same — the + // service-provider dependencies don't change per op, only the methods + // on the provider do. + storageProvider, err := storage.New(&rootArgs.config, &storage.Dependencies{Log: &log}) + if err != nil { + log.Fatal().Err(err).Msg("failed to create storage provider") + } + memoryStoreProvider, err := memory_store.New(&rootArgs.config, &memory_store.Dependencies{ + Log: &log, + StorageProvider: storageProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create memory store provider") + } + tokenProvider, err := token.New(&rootArgs.config, &token.Dependencies{ + Log: &log, + MemoryStoreProvider: memoryStoreProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create token provider") + } + emailProvider, err := email.New(&rootArgs.config, &email.Dependencies{ + Log: &log, + StorageProvider: storageProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create email provider") + } + smsProvider, err := sms.New(&rootArgs.config, &sms.Dependencies{Log: &log}) + if err != nil { + log.Fatal().Err(err).Msg("failed to create sms provider") + } + auditProvider := audit.New(&audit.Dependencies{ + Log: &log, + StorageProvider: storageProvider, + }) + eventsProvider, err := events.New(&rootArgs.config, &events.Dependencies{ + Log: &log, + StorageProvider: storageProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create events provider") + } + + // Embedded OpenFGA engine, shared init with root.go. Nil (fail-closed) + // when FGA is not configured or init fails — the permission tools fail + // closed while the rest of the MCP surface serves. + authzEngine, closeAuthzEngine := initAuthzEngine(&rootArgs.config, &log) + defer closeAuthzEngine() + + svc, err := service.New(&rootArgs.config, &service.Dependencies{ + Log: &log, + AuditProvider: auditProvider, + AuthzEngine: authzEngine, + EmailProvider: emailProvider, + EventsProvider: eventsProvider, + MemoryStoreProvider: memoryStoreProvider, + SMSProvider: smsProvider, + StorageProvider: storageProvider, + TokenProvider: tokenProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create service provider") + } + + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: &log, + Config: &rootArgs.config, + ServiceProvider: svc, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create grpc server") + } + + mcpSrv, err := mcp.New(&log, grpcSrv.GRPCServer(), mcp.Options{ + Name: "authorizer", + Version: constants.VERSION, + Bearer: mcpArgs.bearer, + AuthorizerURL: mcpArgs.authorizerURL, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create mcp server") + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + <-c + cancel() + }() + + if err := mcpSrv.RunStdio(ctx); err != nil { + log.Error().Err(err).Msg("mcp server exited") + os.Exit(1) + } +} diff --git a/cmd/root.go b/cmd/root.go index 950c2ff6..385a8f75 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,8 +3,10 @@ package cmd import ( "context" "fmt" + "net" "os" "os/signal" + "strconv" "strings" "time" @@ -15,18 +17,18 @@ import ( "github.com/authorizerdev/authorizer/internal/audit" "github.com/authorizerdev/authorizer/internal/authenticators" - "github.com/authorizerdev/authorizer/internal/authorization/engine" - fgaengine "github.com/authorizerdev/authorizer/internal/authorization/engine/openfga" "github.com/authorizerdev/authorizer/internal/config" "github.com/authorizerdev/authorizer/internal/constants" "github.com/authorizerdev/authorizer/internal/email" "github.com/authorizerdev/authorizer/internal/events" + "github.com/authorizerdev/authorizer/internal/grpcsrv" "github.com/authorizerdev/authorizer/internal/http_handlers" "github.com/authorizerdev/authorizer/internal/memory_store" "github.com/authorizerdev/authorizer/internal/metrics" "github.com/authorizerdev/authorizer/internal/oauth" "github.com/authorizerdev/authorizer/internal/rate_limit" "github.com/authorizerdev/authorizer/internal/server" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" "github.com/authorizerdev/authorizer/internal/token" @@ -77,7 +79,10 @@ var ( ) func init() { - f := RootCmd.Flags() + // Persistent so subcommands (`authorizer mcp`) inherit the full server + // flag surface (--database-type, --client-id, --fga-store, ...) and + // share the same rootArgs storage. + f := RootCmd.PersistentFlags() // Server flags f.StringVar(&rootArgs.server.Host, "host", defaultHost, "Host address to listen on") @@ -102,6 +107,14 @@ func init() { f.IntVar(&rootArgs.config.GraphQLMaxAliases, "graphql-max-aliases", 30, "Maximum total number of aliased fields per GraphQL operation") f.Int64Var(&rootArgs.config.GraphQLMaxBodyBytes, "graphql-max-body-bytes", 1<<20, "Maximum allowed GraphQL request body size in bytes (default 1MB)") + // gRPC server flags. Port 9091 avoids collision with the metrics + // listener which defaults to 8081 (and with the HTTP listener on 8080). + f.IntVar(&rootArgs.config.GRPCPort, "grpc-port", 9091, "Port the gRPC server listens on") + f.BoolVar(&rootArgs.config.EnableGRPCReflection, "enable-grpc-reflection", true, "Enable the gRPC server-reflection service") + f.StringVar(&rootArgs.config.GRPCTLSCert, "grpc-tls-cert", "", "Path to the TLS certificate for the gRPC server") + f.StringVar(&rootArgs.config.GRPCTLSKey, "grpc-tls-key", "", "Path to the TLS private key for the gRPC server") + f.BoolVar(&rootArgs.config.GRPCInsecure, "grpc-insecure", false, "Allow the gRPC server to run without TLS (dev only)") + // Organization flags f.StringVar(&rootArgs.config.OrganizationLogo, "organization-logo", defaultOrganizationLogo, "Logo of the organization") f.StringVar(&rootArgs.config.OrganizationName, "organization-name", defaultOrganizationName, "Name of the organization") @@ -335,9 +348,20 @@ func applyFlagDefaults() { // Run the service func runRoot(c *cobra.Command, args []string) { applyFlagDefaults() - if rootArgs.server.HTTPPort == rootArgs.server.MetricsPort { - fmt.Fprintf(os.Stderr, "invalid server ports: --http-port and --metrics-port must differ (metrics are always served on a dedicated listener)\n") - os.Exit(1) + // All three listeners (HTTP, metrics, gRPC) bind concurrently; any + // collision is unrecoverable at runtime, so we fail fast at startup. + ports := map[string]int{ + "--http-port": rootArgs.server.HTTPPort, + "--metrics-port": rootArgs.server.MetricsPort, + "--grpc-port": rootArgs.config.GRPCPort, + } + for nameA, a := range ports { + for nameB, b := range ports { + if nameA < nameB && a == b { + fmt.Fprintf(os.Stderr, "invalid server ports: %s (%d) and %s (%d) must differ — each listener binds independently\n", nameA, a, nameB, b) + os.Exit(1) + } + } } // Refuse to start without an admin secret. The previous default of @@ -464,52 +488,12 @@ func runRoot(c *cobra.Command, args []string) { } defer rateLimitProvider.Close() - // OpenFGA fine-grained authorization engine (embedded, in-process). - // - // Authorizer embeds OpenFGA — it IS the engine. The engine is constructed - // only when an FGA store is configured (--fga-store); otherwise it stays nil - // and the fga_* resolvers fail closed ("fine-grained authorization is not - // enabled"). Routed into GraphQL and session/validate below. - // - // By default FGA reuses the main database (sqlite/postgres/mysql/mariadb); - // --fga-store is only needed when the main DB is unsupported (mongodb, - // dynamodb, etc.) or to point at a dedicated store. FGAStoreConfig() resolves - // this. OpenFGA migrations run on boot for SQL stores (idempotent); memory - // needs none. NOTE: multi-replica deployments should prefer running - // migrations once via an init job — concurrent on-boot migrations rely on - // the migration tool's own locking and add cold-start latency. - // - // Engine-init failure is deliberately NON-fatal: FGA is an optional - // subsystem, so a failure here (e.g. the DB user lacks DDL rights for the - // OpenFGA tables) logs loudly and leaves authzEngine nil — fga_* and the - // permission APIs fail closed while core authentication keeps serving. - var authzEngine engine.AuthorizationEngine - if fgaStore, fgaStoreURL, fgaEnabled := rootArgs.config.FGAStoreConfig(); fgaEnabled { - runMigrations := !strings.EqualFold(fgaStore, fgaengine.StoreMemory) - fgaEngine, ferr := fgaengine.New( - &fgaengine.Config{ - Store: fgaStore, - StoreURL: fgaStoreURL, - StoreName: rootArgs.config.OrganizationName, - RunMigrations: runMigrations, - }, - &fgaengine.Dependencies{Log: &log}, - ) - if ferr != nil { - log.Error().Err(ferr). - Str("fga_store", fgaStore). - Msg("failed to initialize OpenFGA authorization engine; fine-grained authorization is DISABLED (fail-closed) — core auth continues") - } else { - if closer, ok := fgaEngine.(interface{ Close() }); ok { - defer closer.Close() - } - authzEngine = fgaEngine - log.Info(). - Str("fga_store", fgaStore). - Bool("reused_main_db", strings.TrimSpace(rootArgs.config.FGAStore) == ""). - Msg("OpenFGA authorization engine initialized (embedded); routed into GraphQL + session/validate") - } - } + // Embedded OpenFGA authorization engine (optional; nil when --fga-store + // is not configured). NOTE: multi-replica deployments should prefer + // running migrations once via an init job — concurrent on-boot migrations + // rely on the migration tool's own locking and add cold-start latency. + authzEngine, closeAuthzEngine := initAuthzEngine(&rootArgs.config, &log) + defer closeAuthzEngine() // SMS provider smsProvider, err := sms.New(&rootArgs.config, &sms.Dependencies{ @@ -549,6 +533,23 @@ func runRoot(c *cobra.Command, args []string) { StorageProvider: storageProvider, }) + // Transport-agnostic service layer that hosts public-API operations. + // GraphQL, gRPC, and REST surfaces all delegate to this. + serviceProvider, err := service.New(&rootArgs.config, &service.Dependencies{ + Log: &log, + AuditProvider: auditProvider, + AuthzEngine: authzEngine, + EmailProvider: emailProvider, + EventsProvider: eventsProvider, + MemoryStoreProvider: memoryStoreProvider, + SMSProvider: smsProvider, + StorageProvider: storageProvider, + TokenProvider: tokenProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create service provider") + } + httpProvider, err := http_handlers.New(&rootArgs.config, &http_handlers.Dependencies{ Log: &log, AuditProvider: auditProvider, @@ -561,16 +562,33 @@ func runRoot(c *cobra.Command, args []string) { TokenProvider: tokenProvider, OAuthProvider: oauthProvider, RateLimitProvider: rateLimitProvider, + ServiceProvider: serviceProvider, AuthzEngine: authzEngine, }) if err != nil { log.Fatal().Err(err).Msg("failed to create http provider") } + + // gRPC server — listens on --grpc-port. The REST gateway built by + // server.Run wraps this same gRPC server in-process so /v1/* REST + // calls translate to local gRPC method invocations (no network hop). + grpcAddr := net.JoinHostPort(rootArgs.server.Host, strconv.Itoa(rootArgs.config.GRPCPort)) + grpcSrv, err := grpcsrv.New(grpcAddr, &grpcsrv.Dependencies{ + Log: &log, + Config: &rootArgs.config, + ServiceProvider: serviceProvider, + }) + if err != nil { + log.Fatal().Err(err).Msg("failed to create grpc server") + } + rootArgs.server.GRPCPort = rootArgs.config.GRPCPort + // Prepare server deps := &server.Dependencies{ Log: &log, AppConfig: &rootArgs.config, HTTPProvider: httpProvider, + GRPCServer: grpcSrv, } // Create the server svr, err := server.New(&rootArgs.server, deps) diff --git a/docs/grpc-rest-api-spec.md b/docs/grpc-rest-api-spec.md index 305c01fe..b2b87a9a 100644 --- a/docs/grpc-rest-api-spec.md +++ b/docs/grpc-rest-api-spec.md @@ -12,7 +12,48 @@ Authorizer uses a single-source-of-truth Protobuf definition to generate: ### Package & Versioning - **Package**: `authorizer.v1` - **Directory Structure**: `proto/authorizer/v1/` -- **API Versioning**: Hardcoded in HTTP paths as `/api/v1/...` and tracked via Protobuf package versioning. +- **API Versioning**: Hardcoded in HTTP paths as `/v1/...` and tracked via Protobuf package versioning. + +### REST Naming Conventions (Stripe-aligned) + +Authorizer's REST surface follows the **Stripe "gold standard" REST conventions**. Stripe is widely regarded as the benchmark for developer-facing REST API design, and aligning with it keeps the surface consistent and unsurprising: + +1. **`snake_case` everywhere — paths, query parameters, and JSON bodies.** + Multi-word path segments use underscores, e.g. `/v1/magic_link_login`, + `/v1/verify_email`, `/v1/validate_jwt_token`. This mirrors Stripe's own + paths (`/v1/payment_intents`, `/v1/setup_intents`) and, critically, keeps a + **single** naming style across the entire product: the path segment, the + gRPC method identifier, the GraphQL operation name, and every JSON field are + all `snake_case`. (The gateway sets `UseProtoNames=true` so response bodies + stay `snake_case` rather than protobuf-default `camelCase`.) + + > We deliberately do **not** use `kebab-case` paths. While some guides + > (Microsoft, Google AIP) and Keycloak prefer hyphens, mixing hyphenated + > paths with `snake_case` bodies/operations would introduce a second naming + > convention. Internal consistency wins; Stripe and Auth0 + > (`/dbconnections/change_password`) set the precedent. + +2. **HTTP method reflects effect.** `GET` is reserved for safe, side-effect-free + reads (`meta`, `profile`, `permissions`). Anything that mutates server state + — including `logout` (it clears the session and is audit-logged) — uses + `POST`. A mutating `GET` would violate RFC 9110 §9.2.1 and expose the + endpoint to CSRF. + +3. **Path prefix `/v1`** (not `/api/v1`) — the version is the first segment, + matching Stripe's `/v1/...`. + +4. **Stable, snake_case error envelope** on every `/v1` endpoint: + + ```json + { "code": "invalid_argument", "message": "email or phone number is required" } + ``` + + The HTTP status is derived from the gRPC status code + (`invalid_argument`→400, `unauthenticated`→401, `permission_denied`→403, + `not_found`→404, `failed_precondition`→400, `internal`→500). The service + layer classifies each error with a transport-neutral `ErrorKind` + (`internal/service/errors.go`); the gRPC `ErrorMap` interceptor turns that + into a status code, and grpc-gateway maps the code to the HTTP status. ### Ecosystem Tooling - **`buf`**: Managed build system for Protobuf. @@ -29,26 +70,31 @@ All public GraphQL queries and mutations are mapped to RPC methods. Terminologie ### 2.1. Authentication Service (`authorizer.v1.AuthorizerService`) +Paths are `snake_case` under `/v1` (see "REST Naming Conventions" above). The +gRPC method name is the `PascalCase` form of the same identifier. + | RPC Method | GraphQL Equivalent | HTTP Path | Permissions | | :--- | :--- | :--- | :--- | -| `Signup` | `signup` | `POST /api/v1/signup` | Public | -| `Login` | `login` | `POST /api/v1/login` | Public | -| `MagicLinkLogin` | `magic_link_login` | `POST /api/v1/magic-link-login` | Public | -| `Logout` | `logout` | `POST /api/v1/logout` | Authenticated | -| `VerifyEmail` | `verify_email` | `POST /api/v1/verify-email` | Public | -| `ResendVerifyEmail` | `resend_verify_email` | `POST /api/v1/resend-verify-email` | Public | -| `ForgotPassword` | `forgot_password` | `POST /api/v1/forgot-password` | Public | -| `ResetPassword` | `reset_password` | `POST /api/v1/reset-password` | Public | -| `VerifyOtp` | `verify_otp` | `POST /api/v1/verify-otp` | Public | -| `ResendOtp` | `resend_otp` | `POST /api/v1/resend-otp` | Public | -| `Revoke` | `revoke` | `POST /api/v1/revoke` | Authenticated | -| `DeactivateAccount` | `deactivate_account` | `DELETE /api/v1/account` | Authenticated | -| `GetMeta` | `meta` | `GET /api/v1/meta` | Public | -| `GetSession` | `session` | `POST /api/v1/session` | Authenticated | -| `GetProfile` | `profile` | `GET /api/v1/profile` | Authenticated | -| `ValidateJwtToken` | `validate_jwt_token` | `POST /api/v1/validate-jwt` | Public/Service | -| `ValidateSession` | `validate_session` | `POST /api/v1/validate-session` | Public/Service | -| `GetPermissions` | `permissions` | `GET /api/v1/permissions` | Authenticated | +| `Signup` | `signup` | `POST /v1/signup` | Public | +| `Login` | `login` | `POST /v1/login` | Public | +| `MagicLinkLogin` | `magic_link_login` | `POST /v1/magic_link_login` | Public | +| `Logout` | `logout` | `POST /v1/logout` | Authenticated | +| `VerifyEmail` | `verify_email` | `POST /v1/verify_email` | Public | +| `ResendVerifyEmail` | `resend_verify_email` | `POST /v1/resend_verify_email` | Public | +| `ForgotPassword` | `forgot_password` | `POST /v1/forgot_password` | Public | +| `ResetPassword` | `reset_password` | `POST /v1/reset_password` | Public | +| `VerifyOtp` | `verify_otp` | `POST /v1/verify_otp` | Public | +| `ResendOtp` | `resend_otp` | `POST /v1/resend_otp` | Public | +| `Revoke` | `revoke` | `POST /v1/revoke` | Authenticated | +| `UpdateProfile` | `update_profile` | `POST /v1/update_profile` | Authenticated | +| `DeactivateAccount` | `deactivate_account` | `POST /v1/deactivate_account` | Authenticated | +| `Meta` | `meta` | `GET /v1/meta` | Public | +| `Session` | `session` | `POST /v1/session` | Authenticated | +| `Profile` | `profile` | `GET /v1/profile` | Authenticated | +| `ValidateJwtToken` | `validate_jwt_token` | `POST /v1/validate_jwt_token` | Public/Service | +| `ValidateSession` | `validate_session` | `POST /v1/validate_session` | Public/Service | +| `CheckPermissions` | `check_permissions` | `POST /v1/check_permissions` | Authenticated | +| `ListPermissions` | `list_permissions` | `POST /v1/list_permissions` | Authenticated | ### 2.2. OIDC & OAuth2 REST Endpoints The following endpoints remain as pure HTTP handlers to comply with strict OIDC/OAuth2 protocol requirements (redirects, form-encoding): @@ -63,23 +109,59 @@ The following endpoints remain as pure HTTP handlers to comply with strict OIDC/ --- -## 3. Documentation & Commenting Standards +## 3. Required Relations: Fine-Grained Authorization Gates + +The `Session`, `ValidateJwtToken`, and `ValidateSession` RPCs accept an optional `required_relations` field that gates the response on fine-grained authorization checks. When provided, each (relation, object) pair is evaluated against the authenticated subject with AND semantics: + +**Example**: Session RPC with required relations + +```json +{ + "roles": ["admin"], + "scope": ["read:profile"], + "required_relations": [ + { + "relation": "can_manage", + "object": "organization:1" + }, + { + "relation": "can_edit", + "object": "workspace:42" + } + ] +} +``` + +The session is returned only if the caller can both `can_manage` the organization AND `can_edit` the workspace. If any check fails, the RPC returns `permission_denied`. This is fail-closed behavior. + +**When to Use**: +- Implementing conditional access policies. +- Gating session establishment on resource-specific permissions. +- Building fine-grained authorization directly into token validation. + +**Compatibility**: +- If fine-grained authorization is not enabled (`--fga-store` not set), providing `required_relations` returns an error. +- Omitting `required_relations` (the default) skips these checks. + +--- + +## 4. Documentation & Commenting Standards To ensure the generated API documentation (Swagger/OpenAPI) and TypeScript clients are well-documented, the following standards MUST be followed in all `.proto` files: -### 3.1. General Principles +### 4.1. General Principles - Use `//` for all descriptions. - Every RPC, Message, and Field must have a description. - Start with a clear summary line, followed by details on constraints or behavior. -### 3.2. Field Metadata Labels +### 4.2. Field Metadata Labels Include explicit labels in field comments to denote behavior: - `// Required.` - For fields that must be provided. - `// Optional.` - For fields that can be omitted. - `// Read-only.` - For fields that are only populated by the server in responses. - `// Output only.` - Similar to read-only, specifically for create/update requests where the field is ignored. -### 3.3. Permission Blocks +### 4.3. Permission Blocks Every RPC method comment must include a standardized permission block: ```protobuf // [Description of the RPC] @@ -90,7 +172,7 @@ Every RPC method comment must include a standardized permission block: --- -## 4. Protocol Buffer Definition Samples +## 5. Protocol Buffer Definition Samples ### Permissions, Validations & Documentation Using the Qdrant pattern for permissions, `protovalidate` for field rules, and the documentation standards defined above. @@ -172,7 +254,106 @@ message SignUpRequest { --- -## 5. Migration Strategy: Interface Pattern +## 4.1. Fine-Grained Authorization: Permission Check RPCs + +The `CheckPermissions` and `ListPermissions` RPCs provide OpenFGA-backed fine-grained authorization. They are optional — they fail gracefully when fine-grained authorization is not enabled (no `--fga-store`). + +### CheckPermissions + +Evaluates one or more permission checks in a single batch call. Answers the question: "Does the subject have on ?" + +**HTTP**: `POST /v1/check_permissions` + +**Request**: +```json +{ + "checks": [ + { + "relation": "can_edit", + "object": "document:12345", + "contextual_tuples": [ + { + "user": "user:alice", + "relation": "viewer", + "object": "document:12345" + } + ] + } + ], + "user": "user:alice" +} +``` + +**Response**: +```json +{ + "results": [ + { + "relation": "can_edit", + "object": "document:12345", + "allowed": false + } + ] +} +``` + +**Subject Resolution**: +- If `user` is omitted, the check uses the authenticated caller's subject from the context. +- If `user` is provided, it is honored only if: (1) the caller is a super-admin, or (2) the `user` matches the caller's own subject (self-check). +- Anything else is rejected with `unauthenticated` or `permission_denied`. + +**Fail-Closed**: If fine-grained authorization is not enabled, this RPC returns an error. + +### ListPermissions + +Enumerates what the subject can access, optionally filtered by relation and/or object type. Answers: "Which s can I ?" + +**HTTP**: `POST /v1/list_permissions` + +**Request**: +```json +{ + "relation": "can_view", + "object_type": "document", + "user": "user:alice" +} +``` + +**Response**: +```json +{ + "objects": [ + "document:12345", + "document:67890" + ], + "permissions": [ + { + "object": "document:12345", + "relation": "can_view" + }, + { + "object": "document:67890", + "relation": "can_view" + } + ], + "truncated": false +} +``` + +**Parameters**: +- `relation` (optional): Filter by relation (e.g., `can_view`, `can_edit`). +- `object_type` (optional): Filter by object type (e.g., `document`, `folder`). +- `user` (optional): Subject to enumerate permissions for (same trust rules as `CheckPermissions`). + +**Caps & Truncation**: +- Results are capped at 1000 entries. +- `truncated` is `true` if more permissions exist; the caller must refine filters or paginate. + +**Fail-Closed**: If fine-grained authorization is not enabled, this RPC returns an error. + +--- + +## 6. Migration Strategy: Interface Pattern To avoid duplicating business logic between GraphQL and gRPC, all logic is moved to a unified `service.Provider` interface. @@ -203,7 +384,10 @@ type Provider interface { GetProfile(ctx context.Context) (*User, error) ValidateJwtToken(ctx context.Context, params *ValidateJWTTokenRequest) (*ValidateJWTTokenResponse, error) ValidateSession(ctx context.Context, params *ValidateSessionRequest) (*ValidateSessionResponse, error) - GetPermissions(ctx context.Context) ([]*Permission, error) + + // Fine-grained Authorization (OpenFGA-backed) + CheckPermissions(ctx context.Context, params *CheckPermissionsRequest) (*CheckPermissionsResponse, error) + ListPermissions(ctx context.Context, params *ListPermissionsRequest) (*ListPermissionsResponse, error) } ``` @@ -247,7 +431,12 @@ func (s *Server) Signup(ctx context.Context, req *pb.SignUpRequest) (*pb.AuthRes --- -## 6. Detailed Mapping Table +## 7. Detailed Mapping Table + +> **Note:** The table in §2.1 is the authoritative, as-implemented mapping +> (`snake_case` paths under `/v1`). The table below is the original design +> sketch retained for historical context; where it differs (hyphenated paths, +> `/api/v1` prefix, `Get*`/`DELETE`/`PUT` shapes), §2.1 wins. | gRPC Method | GraphQL Field | REST Gateway Path | Perms | Logic Method | | :--- | :--- | :--- | :--- | :--- | @@ -269,11 +458,12 @@ func (s *Server) Signup(ctx context.Context, req *pb.SignUpRequest) (*pb.AuthRes | `GetProfile` | `profile` | `GET /api/v1/profile` | `auth` | `GetProfile` | | `ValidateJwtToken` | `validate_jwt_token` | `POST /api/v1/validate-jwt` | - | `ValidateJwtToken` | | `ValidateSession` | `validate_session` | `POST /api/v1/validate-session` | - | `ValidateSession` | -| `GetPermissions` | `permissions` | `GET /api/v1/permissions` | `auth` | `GetPermissions` | +| `CheckPermissions` | `check_permissions` | `POST /api/v1/check-permissions` | `auth` | `CheckPermissions` | +| `ListPermissions` | `list_permissions` | `POST /api/v1/list-permissions` | `auth` | `ListPermissions` | --- -## 7. Testing Strategy +## 8. Testing Strategy ### 1. Service Logic Tests Unit tests for the `internal/service` implementation using mock storage and memory providers. These tests ensure business logic correctness regardless of the transport layer. @@ -288,7 +478,7 @@ Assert that `protovalidate` rules (e.g., email format) correctly reject invalid --- -## 8. Development Workflow +## 9. Development Workflow 1. **Modify Proto**: Edit files in `proto/authorizer/v1/`. 2. **Generate Code**: diff --git a/gen/go/authorizer/common/v1/annotations.pb.go b/gen/go/authorizer/common/v1/annotations.pb.go new file mode 100644 index 00000000..8357924f --- /dev/null +++ b/gen/go/authorizer/common/v1/annotations.pb.go @@ -0,0 +1,332 @@ +// Custom proto options that decorate Authorizer service methods with +// authorization, audit, MCP-exposure, and visibility metadata. +// +// All options live on MethodOptions and are read at runtime by the gRPC +// server (auth/permission/audit interceptors) and by the MCP server, and +// at codegen time by the OpenAPI generator. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/common/v1/annotations.proto + +package commonv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + descriptorpb "google.golang.org/protobuf/types/descriptorpb" + 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) +) + +// PermissionRequirement names one (resource, scope) pair the caller must hold +// to invoke the RPC. Multiple values on a method are AND-combined (the caller +// must hold *all* of them); to express OR semantics, list the alternatives in +// a single PermissionRequirement with a wildcard scope and let the policy +// engine evaluate. +type PermissionRequirement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Resource name as registered in the authz subsystem (e.g. "user", + // "webhook"). Required. + Resource string `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + // Scope name (e.g. "read", "write", "delete"). Required. + Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` +} + +func (x *PermissionRequirement) Reset() { + *x = PermissionRequirement{} + mi := &file_authorizer_common_v1_annotations_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PermissionRequirement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PermissionRequirement) ProtoMessage() {} + +func (x *PermissionRequirement) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_common_v1_annotations_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PermissionRequirement.ProtoReflect.Descriptor instead. +func (*PermissionRequirement) Descriptor() ([]byte, []int) { + return file_authorizer_common_v1_annotations_proto_rawDescGZIP(), []int{0} +} + +func (x *PermissionRequirement) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *PermissionRequirement) GetScope() string { + if x != nil { + return x.Scope + } + return "" +} + +// McpTool marks an RPC as exposed via the Authorizer MCP server. +// Defaults to "not exposed" when the option is absent. +type McpTool struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Whether the RPC is reachable as an MCP tool. Default false. + Exposed bool `protobuf:"varint,1,opt,name=exposed,proto3" json:"exposed,omitempty"` + // Optional override for the tool name surfaced to MCP clients. When unset, + // the snake_case form of the RPC method name is used. + ToolName string `protobuf:"bytes,2,opt,name=tool_name,json=toolName,proto3" json:"tool_name,omitempty"` + // Hint to the MCP host that the tool mutates state in a way that warrants + // explicit user confirmation (e.g. delete operations). + Destructive bool `protobuf:"varint,3,opt,name=destructive,proto3" json:"destructive,omitempty"` +} + +func (x *McpTool) Reset() { + *x = McpTool{} + mi := &file_authorizer_common_v1_annotations_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *McpTool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*McpTool) ProtoMessage() {} + +func (x *McpTool) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_common_v1_annotations_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use McpTool.ProtoReflect.Descriptor instead. +func (*McpTool) Descriptor() ([]byte, []int) { + return file_authorizer_common_v1_annotations_proto_rawDescGZIP(), []int{1} +} + +func (x *McpTool) GetExposed() bool { + if x != nil { + return x.Exposed + } + return false +} + +func (x *McpTool) GetToolName() string { + if x != nil { + return x.ToolName + } + return "" +} + +func (x *McpTool) GetDestructive() bool { + if x != nil { + return x.Destructive + } + return false +} + +var file_authorizer_common_v1_annotations_proto_extTypes = []protoimpl.ExtensionInfo{ + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: ([]*PermissionRequirement)(nil), + Field: 50001, + Name: "authorizer.common.v1.required_permissions", + Tag: "bytes,50001,rep,name=required_permissions", + Filename: "authorizer/common/v1/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*McpTool)(nil), + Field: 50002, + Name: "authorizer.common.v1.mcp_tool", + Tag: "bytes,50002,opt,name=mcp_tool", + Filename: "authorizer/common/v1/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50003, + Name: "authorizer.common.v1.audit_log", + Tag: "varint,50003,opt,name=audit_log", + Filename: "authorizer/common/v1/annotations.proto", + }, + { + ExtendedType: (*descriptorpb.MethodOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 50004, + Name: "authorizer.common.v1.public", + Tag: "varint,50004,opt,name=public", + Filename: "authorizer/common/v1/annotations.proto", + }, +} + +// Extension fields to descriptorpb.MethodOptions. +var ( + // All permissions the caller must hold (AND). Empty means "no authz check + // beyond the auth interceptor". + // + // repeated authorizer.common.v1.PermissionRequirement required_permissions = 50001; + E_RequiredPermissions = &file_authorizer_common_v1_annotations_proto_extTypes[0] + // MCP-tool exposure metadata; absent means "not exposed". + // + // optional authorizer.common.v1.McpTool mcp_tool = 50002; + E_McpTool = &file_authorizer_common_v1_annotations_proto_extTypes[1] + // When true, the audit interceptor records an entry for the invocation. + // + // optional bool audit_log = 50003; + E_AuditLog = &file_authorizer_common_v1_annotations_proto_extTypes[2] + // When true, the auth interceptor allows unauthenticated callers. Use for + // login, signup, magic-link request, password-reset request, etc. + // + // optional bool public = 50004; + E_Public = &file_authorizer_common_v1_annotations_proto_extTypes[3] +) + +var File_authorizer_common_v1_annotations_proto protoreflect.FileDescriptor + +var file_authorizer_common_v1_annotations_proto_rawDesc = []byte{ + 0x0a, 0x26, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x20, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x49, 0x0a, 0x15, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x22, 0x62, 0x0a, 0x07, 0x4d, + 0x63, 0x70, 0x54, 0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x69, 0x76, 0x65, 0x3a, + 0x80, 0x01, 0x0a, 0x14, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd1, 0x86, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x13, 0x72, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x3a, 0x5a, 0x0a, 0x08, 0x6d, 0x63, 0x70, 0x5f, 0x74, 0x6f, 0x6f, 0x6c, 0x12, 0x1e, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd2, + 0x86, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x63, + 0x70, 0x54, 0x6f, 0x6f, 0x6c, 0x52, 0x07, 0x6d, 0x63, 0x70, 0x54, 0x6f, 0x6f, 0x6c, 0x3a, 0x3d, + 0x0a, 0x09, 0x61, 0x75, 0x64, 0x69, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x12, 0x1e, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd3, 0x86, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x75, 0x64, 0x69, 0x74, 0x4c, 0x6f, 0x67, 0x3a, 0x38, 0x0a, + 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xd4, 0x86, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x42, 0xe8, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x10, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x64, + 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x43, 0x58, 0xaa, 0x02, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0xca, + 0x02, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x16, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, + 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authorizer_common_v1_annotations_proto_rawDescOnce sync.Once + file_authorizer_common_v1_annotations_proto_rawDescData = file_authorizer_common_v1_annotations_proto_rawDesc +) + +func file_authorizer_common_v1_annotations_proto_rawDescGZIP() []byte { + file_authorizer_common_v1_annotations_proto_rawDescOnce.Do(func() { + file_authorizer_common_v1_annotations_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_common_v1_annotations_proto_rawDescData) + }) + return file_authorizer_common_v1_annotations_proto_rawDescData +} + +var file_authorizer_common_v1_annotations_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_authorizer_common_v1_annotations_proto_goTypes = []any{ + (*PermissionRequirement)(nil), // 0: authorizer.common.v1.PermissionRequirement + (*McpTool)(nil), // 1: authorizer.common.v1.McpTool + (*descriptorpb.MethodOptions)(nil), // 2: google.protobuf.MethodOptions +} +var file_authorizer_common_v1_annotations_proto_depIdxs = []int32{ + 2, // 0: authorizer.common.v1.required_permissions:extendee -> google.protobuf.MethodOptions + 2, // 1: authorizer.common.v1.mcp_tool:extendee -> google.protobuf.MethodOptions + 2, // 2: authorizer.common.v1.audit_log:extendee -> google.protobuf.MethodOptions + 2, // 3: authorizer.common.v1.public:extendee -> google.protobuf.MethodOptions + 0, // 4: authorizer.common.v1.required_permissions:type_name -> authorizer.common.v1.PermissionRequirement + 1, // 5: authorizer.common.v1.mcp_tool:type_name -> authorizer.common.v1.McpTool + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 4, // [4:6] is the sub-list for extension type_name + 0, // [0:4] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_authorizer_common_v1_annotations_proto_init() } +func file_authorizer_common_v1_annotations_proto_init() { + if File_authorizer_common_v1_annotations_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_common_v1_annotations_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 4, + NumServices: 0, + }, + GoTypes: file_authorizer_common_v1_annotations_proto_goTypes, + DependencyIndexes: file_authorizer_common_v1_annotations_proto_depIdxs, + MessageInfos: file_authorizer_common_v1_annotations_proto_msgTypes, + ExtensionInfos: file_authorizer_common_v1_annotations_proto_extTypes, + }.Build() + File_authorizer_common_v1_annotations_proto = out.File + file_authorizer_common_v1_annotations_proto_rawDesc = nil + file_authorizer_common_v1_annotations_proto_goTypes = nil + file_authorizer_common_v1_annotations_proto_depIdxs = nil +} diff --git a/gen/go/authorizer/common/v1/errors.pb.go b/gen/go/authorizer/common/v1/errors.pb.go new file mode 100644 index 00000000..5e9bb211 --- /dev/null +++ b/gen/go/authorizer/common/v1/errors.pb.go @@ -0,0 +1,217 @@ +// Domain-specific error reasons attached to google.rpc.Status via ErrorInfo. +// +// Wire shape: handlers return standard gRPC status codes (e.g. +// PERMISSION_DENIED, INVALID_ARGUMENT) and attach an ErrorInfo detail whose +// `reason` field is one of the enum values below. The gateway surfaces the +// same ErrorInfo in the REST response body. Clients should branch on the +// enum, not on the human-readable message. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/common/v1/errors.proto + +package commonv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ErrorReason int32 + +const ( + ErrorReason_ERROR_REASON_UNSPECIFIED ErrorReason = 0 + // Authentication failed (bad credentials, expired token, missing cookie). + // Maps to gRPC UNAUTHENTICATED / HTTP 401. + ErrorReason_ERROR_REASON_INVALID_CREDENTIALS ErrorReason = 1 + // Caller is authenticated but lacks the required permission. + // Maps to PERMISSION_DENIED / HTTP 403. + ErrorReason_ERROR_REASON_PERMISSION_DENIED ErrorReason = 2 + // The targeted resource does not exist. NOT_FOUND / 404. + ErrorReason_ERROR_REASON_NOT_FOUND ErrorReason = 3 + // A unique constraint was violated (e.g. email already registered). + // ALREADY_EXISTS / 409. + ErrorReason_ERROR_REASON_ALREADY_EXISTS ErrorReason = 4 + // Request validation failed beyond what protovalidate caught + // (cross-field, business-rule). INVALID_ARGUMENT / 400. + ErrorReason_ERROR_REASON_INVALID_REQUEST ErrorReason = 5 + // A required identity-verification step has not been completed + // (email not verified, phone not verified, MFA required). FAILED_PRECONDITION / 412. + ErrorReason_ERROR_REASON_VERIFICATION_REQUIRED ErrorReason = 6 + // The current configuration disables the requested operation + // (sign-up disabled, magic-link login disabled, etc.). FAILED_PRECONDITION / 412. + ErrorReason_ERROR_REASON_OPERATION_DISABLED ErrorReason = 7 + // Caller exceeded the configured rate limit. RESOURCE_EXHAUSTED / 429. + ErrorReason_ERROR_REASON_RATE_LIMITED ErrorReason = 8 + // A verification token (email, password reset, magic link, OTP) is expired + // or has already been consumed. FAILED_PRECONDITION / 412. + ErrorReason_ERROR_REASON_TOKEN_EXPIRED ErrorReason = 9 + // The account is deactivated or its access has been revoked by an admin. + // FAILED_PRECONDITION / 412. + ErrorReason_ERROR_REASON_ACCOUNT_DEACTIVATED ErrorReason = 10 +) + +// Enum value maps for ErrorReason. +var ( + ErrorReason_name = map[int32]string{ + 0: "ERROR_REASON_UNSPECIFIED", + 1: "ERROR_REASON_INVALID_CREDENTIALS", + 2: "ERROR_REASON_PERMISSION_DENIED", + 3: "ERROR_REASON_NOT_FOUND", + 4: "ERROR_REASON_ALREADY_EXISTS", + 5: "ERROR_REASON_INVALID_REQUEST", + 6: "ERROR_REASON_VERIFICATION_REQUIRED", + 7: "ERROR_REASON_OPERATION_DISABLED", + 8: "ERROR_REASON_RATE_LIMITED", + 9: "ERROR_REASON_TOKEN_EXPIRED", + 10: "ERROR_REASON_ACCOUNT_DEACTIVATED", + } + ErrorReason_value = map[string]int32{ + "ERROR_REASON_UNSPECIFIED": 0, + "ERROR_REASON_INVALID_CREDENTIALS": 1, + "ERROR_REASON_PERMISSION_DENIED": 2, + "ERROR_REASON_NOT_FOUND": 3, + "ERROR_REASON_ALREADY_EXISTS": 4, + "ERROR_REASON_INVALID_REQUEST": 5, + "ERROR_REASON_VERIFICATION_REQUIRED": 6, + "ERROR_REASON_OPERATION_DISABLED": 7, + "ERROR_REASON_RATE_LIMITED": 8, + "ERROR_REASON_TOKEN_EXPIRED": 9, + "ERROR_REASON_ACCOUNT_DEACTIVATED": 10, + } +) + +func (x ErrorReason) Enum() *ErrorReason { + p := new(ErrorReason) + *p = x + return p +} + +func (x ErrorReason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ErrorReason) Descriptor() protoreflect.EnumDescriptor { + return file_authorizer_common_v1_errors_proto_enumTypes[0].Descriptor() +} + +func (ErrorReason) Type() protoreflect.EnumType { + return &file_authorizer_common_v1_errors_proto_enumTypes[0] +} + +func (x ErrorReason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ErrorReason.Descriptor instead. +func (ErrorReason) EnumDescriptor() ([]byte, []int) { + return file_authorizer_common_v1_errors_proto_rawDescGZIP(), []int{0} +} + +var File_authorizer_common_v1_errors_proto protoreflect.FileDescriptor + +var file_authorizer_common_v1_errors_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2a, 0x86, 0x03, 0x0a, 0x0b, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x24, 0x0a, 0x20, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, + 0x43, 0x52, 0x45, 0x44, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x53, 0x10, 0x01, 0x12, 0x22, 0x0a, + 0x1e, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x45, + 0x52, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4e, 0x49, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x1f, 0x0a, + 0x1b, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x41, 0x4c, + 0x52, 0x45, 0x41, 0x44, 0x59, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x53, 0x10, 0x04, 0x12, 0x20, + 0x0a, 0x1c, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, + 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x05, + 0x12, 0x26, 0x0a, 0x22, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, + 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, 0x06, 0x12, 0x23, 0x0a, 0x1f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x07, 0x12, 0x1d, 0x0a, + 0x19, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x41, + 0x54, 0x45, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x10, 0x08, 0x12, 0x1e, 0x0a, 0x1a, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x4f, 0x4b, + 0x45, 0x4e, 0x5f, 0x45, 0x58, 0x50, 0x49, 0x52, 0x45, 0x44, 0x10, 0x09, 0x12, 0x24, 0x0a, 0x20, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x41, 0x43, 0x43, + 0x4f, 0x55, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x41, 0x43, 0x54, 0x49, 0x56, 0x41, 0x54, 0x45, 0x44, + 0x10, 0x0a, 0x42, 0xe3, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, + 0x0b, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x64, 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x43, 0x58, 0xaa, 0x02, + 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x16, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x3a, 0x43, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authorizer_common_v1_errors_proto_rawDescOnce sync.Once + file_authorizer_common_v1_errors_proto_rawDescData = file_authorizer_common_v1_errors_proto_rawDesc +) + +func file_authorizer_common_v1_errors_proto_rawDescGZIP() []byte { + file_authorizer_common_v1_errors_proto_rawDescOnce.Do(func() { + file_authorizer_common_v1_errors_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_common_v1_errors_proto_rawDescData) + }) + return file_authorizer_common_v1_errors_proto_rawDescData +} + +var file_authorizer_common_v1_errors_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_authorizer_common_v1_errors_proto_goTypes = []any{ + (ErrorReason)(0), // 0: authorizer.common.v1.ErrorReason +} +var file_authorizer_common_v1_errors_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_authorizer_common_v1_errors_proto_init() } +func file_authorizer_common_v1_errors_proto_init() { + if File_authorizer_common_v1_errors_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_common_v1_errors_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_authorizer_common_v1_errors_proto_goTypes, + DependencyIndexes: file_authorizer_common_v1_errors_proto_depIdxs, + EnumInfos: file_authorizer_common_v1_errors_proto_enumTypes, + }.Build() + File_authorizer_common_v1_errors_proto = out.File + file_authorizer_common_v1_errors_proto_rawDesc = nil + file_authorizer_common_v1_errors_proto_goTypes = nil + file_authorizer_common_v1_errors_proto_depIdxs = nil +} diff --git a/gen/go/authorizer/common/v1/pagination.pb.go b/gen/go/authorizer/common/v1/pagination.pb.go new file mode 100644 index 00000000..1b85cb55 --- /dev/null +++ b/gen/go/authorizer/common/v1/pagination.pb.go @@ -0,0 +1,265 @@ +// Pagination types shared across List RPCs. +// +// The Authorizer GraphQL surface uses page+limit (offset-based) pagination. +// To keep the proto surface familiar for current users *and* compatible with +// AIP-158 (page_token-based) clients, both shapes are accepted on +// PaginationRequest; the server picks page_token when set, else falls back to +// page+limit. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/common/v1/pagination.proto + +package commonv1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PaginationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // 1-based page number. Ignored when page_token is set. Default 1. + Page int64 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + // Page size. Server enforces an upper bound (typically 100). Default 10. + Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Opaque cursor returned by the previous List call's `next_page_token`. + // Preferred for new clients (AIP-158). When set, `page` is ignored. + PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` +} + +func (x *PaginationRequest) Reset() { + *x = PaginationRequest{} + mi := &file_authorizer_common_v1_pagination_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PaginationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaginationRequest) ProtoMessage() {} + +func (x *PaginationRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_common_v1_pagination_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaginationRequest.ProtoReflect.Descriptor instead. +func (*PaginationRequest) Descriptor() ([]byte, []int) { + return file_authorizer_common_v1_pagination_proto_rawDescGZIP(), []int{0} +} + +func (x *PaginationRequest) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *PaginationRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *PaginationRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type Pagination struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Limit int64 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Page int64 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` + Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + Total int64 `protobuf:"varint,4,opt,name=total,proto3" json:"total,omitempty"` + // Opaque cursor for the next page; empty when no more pages. + NextPageToken string `protobuf:"bytes,5,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` +} + +func (x *Pagination) Reset() { + *x = Pagination{} + mi := &file_authorizer_common_v1_pagination_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Pagination) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Pagination) ProtoMessage() {} + +func (x *Pagination) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_common_v1_pagination_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Pagination.ProtoReflect.Descriptor instead. +func (*Pagination) Descriptor() ([]byte, []int) { + return file_authorizer_common_v1_pagination_proto_rawDescGZIP(), []int{1} +} + +func (x *Pagination) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *Pagination) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *Pagination) GetOffset() int64 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *Pagination) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *Pagination) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +var File_authorizer_common_v1_pagination_proto protoreflect.FileDescriptor + +var file_authorizer_common_v1_pagination_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1b, 0x62, + 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x71, 0x0a, 0x11, 0x50, 0x61, + 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1b, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x22, 0x02, 0x28, 0x00, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x20, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0a, 0xba, 0x48, 0x07, + 0x22, 0x05, 0x18, 0xe8, 0x07, 0x28, 0x00, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x8c, 0x01, + 0x0a, 0x0a, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x14, + 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x42, 0xe7, 0x01, 0x0a, + 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x50, 0x61, 0x67, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x64, 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x43, 0x58, 0xaa, 0x02, 0x14, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x56, 0x31, 0xca, 0x02, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x41, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, + 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x16, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authorizer_common_v1_pagination_proto_rawDescOnce sync.Once + file_authorizer_common_v1_pagination_proto_rawDescData = file_authorizer_common_v1_pagination_proto_rawDesc +) + +func file_authorizer_common_v1_pagination_proto_rawDescGZIP() []byte { + file_authorizer_common_v1_pagination_proto_rawDescOnce.Do(func() { + file_authorizer_common_v1_pagination_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_common_v1_pagination_proto_rawDescData) + }) + return file_authorizer_common_v1_pagination_proto_rawDescData +} + +var file_authorizer_common_v1_pagination_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_authorizer_common_v1_pagination_proto_goTypes = []any{ + (*PaginationRequest)(nil), // 0: authorizer.common.v1.PaginationRequest + (*Pagination)(nil), // 1: authorizer.common.v1.Pagination +} +var file_authorizer_common_v1_pagination_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_authorizer_common_v1_pagination_proto_init() } +func file_authorizer_common_v1_pagination_proto_init() { + if File_authorizer_common_v1_pagination_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_common_v1_pagination_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_authorizer_common_v1_pagination_proto_goTypes, + DependencyIndexes: file_authorizer_common_v1_pagination_proto_depIdxs, + MessageInfos: file_authorizer_common_v1_pagination_proto_msgTypes, + }.Build() + File_authorizer_common_v1_pagination_proto = out.File + file_authorizer_common_v1_pagination_proto_rawDesc = nil + file_authorizer_common_v1_pagination_proto_goTypes = nil + file_authorizer_common_v1_pagination_proto_depIdxs = nil +} diff --git a/gen/go/authorizer/common/v1/types.pb.go b/gen/go/authorizer/common/v1/types.pb.go new file mode 100644 index 00000000..d35e3e31 --- /dev/null +++ b/gen/go/authorizer/common/v1/types.pb.go @@ -0,0 +1,157 @@ +// Shared scalar-ish types used across Authorizer services. +// +// Kept intentionally tiny: only types that don't naturally belong to a single +// resource service live here. Add a new entry only when at least two services +// share it. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/common/v1/types.proto + +package commonv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + 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) +) + +// AppData is a free-form key/value bag stored against a user. Mirrors the +// GraphQL `Map` scalar. Values are JSON-typed (string, number, bool, null, +// nested object, nested array) to match what the existing storage layer +// accepts. +type AppData struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Value *structpb.Struct `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *AppData) Reset() { + *x = AppData{} + mi := &file_authorizer_common_v1_types_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AppData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppData) ProtoMessage() {} + +func (x *AppData) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_common_v1_types_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppData.ProtoReflect.Descriptor instead. +func (*AppData) Descriptor() ([]byte, []int) { + return file_authorizer_common_v1_types_proto_rawDescGZIP(), []int{0} +} + +func (x *AppData) GetValue() *structpb.Struct { + if x != nil { + return x.Value + } + return nil +} + +var File_authorizer_common_v1_types_proto protoreflect.FileDescriptor + +var file_authorizer_common_v1_types_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x14, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x38, 0x0a, 0x07, 0x41, 0x70, 0x70, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x2d, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x42, 0xe2, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x42, 0x0a, 0x54, + 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x48, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x64, 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x43, 0x58, 0xaa, 0x02, 0x14, 0x41, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, + 0x56, 0x31, 0xca, 0x02, 0x14, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, + 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x20, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x5c, 0x56, 0x31, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x16, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x3a, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authorizer_common_v1_types_proto_rawDescOnce sync.Once + file_authorizer_common_v1_types_proto_rawDescData = file_authorizer_common_v1_types_proto_rawDesc +) + +func file_authorizer_common_v1_types_proto_rawDescGZIP() []byte { + file_authorizer_common_v1_types_proto_rawDescOnce.Do(func() { + file_authorizer_common_v1_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_common_v1_types_proto_rawDescData) + }) + return file_authorizer_common_v1_types_proto_rawDescData +} + +var file_authorizer_common_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_authorizer_common_v1_types_proto_goTypes = []any{ + (*AppData)(nil), // 0: authorizer.common.v1.AppData + (*structpb.Struct)(nil), // 1: google.protobuf.Struct +} +var file_authorizer_common_v1_types_proto_depIdxs = []int32{ + 1, // 0: authorizer.common.v1.AppData.value:type_name -> google.protobuf.Struct + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_authorizer_common_v1_types_proto_init() } +func file_authorizer_common_v1_types_proto_init() { + if File_authorizer_common_v1_types_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_common_v1_types_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_authorizer_common_v1_types_proto_goTypes, + DependencyIndexes: file_authorizer_common_v1_types_proto_depIdxs, + MessageInfos: file_authorizer_common_v1_types_proto_msgTypes, + }.Build() + File_authorizer_common_v1_types_proto = out.File + file_authorizer_common_v1_types_proto_rawDesc = nil + file_authorizer_common_v1_types_proto_goTypes = nil + file_authorizer_common_v1_types_proto_depIdxs = nil +} diff --git a/gen/go/authorizer/v1/authorizer.pb.go b/gen/go/authorizer/v1/authorizer.pb.go new file mode 100644 index 00000000..8843d98d --- /dev/null +++ b/gen/go/authorizer/v1/authorizer.pb.go @@ -0,0 +1,3021 @@ +// AuthorizerService is the single gRPC service that exposes Authorizer's +// public API. Method names match the GraphQL operation names 1:1 +// (snake_case in GraphQL → PascalCase in proto): Signup, Login, +// MagicLinkLogin, VerifyEmail, ResendVerifyEmail, ForgotPassword, +// ResetPassword, VerifyOtp, ResendOtp, UpdateProfile, DeactivateAccount, +// Revoke, Meta, Session, Profile, ValidateJwtToken, ValidateSession, +// CheckPermissions, ListPermissions, Logout. +// +// Why one service: clients consume a single typed client per language, +// discovery is trivial, and the surface mirrors the GraphQL one users +// already know. The trade-off is that we lose resource-oriented evolution +// (no `List/Get/Create` symmetry per resource) — acceptable for an auth +// surface where most operations are stateless verbs anyway. +// +// REST mapping follows a simple rule: +// - GET /v1/{method} when the request body is empty (Meta, Profile, +// Logout) +// - POST /v1/{method} otherwise + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/v1/authorizer.proto + +package authorizerv1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + v1 "github.com/authorizerdev/authorizer/gen/go/authorizer/common/v1" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SignupRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + PhoneNumber string `protobuf:"bytes,2,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + ConfirmPassword string `protobuf:"bytes,4,opt,name=confirm_password,json=confirmPassword,proto3" json:"confirm_password,omitempty"` + GivenName string `protobuf:"bytes,5,opt,name=given_name,json=givenName,proto3" json:"given_name,omitempty"` + FamilyName string `protobuf:"bytes,6,opt,name=family_name,json=familyName,proto3" json:"family_name,omitempty"` + MiddleName string `protobuf:"bytes,7,opt,name=middle_name,json=middleName,proto3" json:"middle_name,omitempty"` + Nickname string `protobuf:"bytes,8,opt,name=nickname,proto3" json:"nickname,omitempty"` + Gender string `protobuf:"bytes,9,opt,name=gender,proto3" json:"gender,omitempty"` + Birthdate string `protobuf:"bytes,10,opt,name=birthdate,proto3" json:"birthdate,omitempty"` + Picture string `protobuf:"bytes,11,opt,name=picture,proto3" json:"picture,omitempty"` + Roles []string `protobuf:"bytes,12,rep,name=roles,proto3" json:"roles,omitempty"` + Scope []string `protobuf:"bytes,13,rep,name=scope,proto3" json:"scope,omitempty"` + RedirectUri string `protobuf:"bytes,14,opt,name=redirect_uri,json=redirectUri,proto3" json:"redirect_uri,omitempty"` + IsMultiFactorAuthEnabled bool `protobuf:"varint,15,opt,name=is_multi_factor_auth_enabled,json=isMultiFactorAuthEnabled,proto3" json:"is_multi_factor_auth_enabled,omitempty"` + State string `protobuf:"bytes,16,opt,name=state,proto3" json:"state,omitempty"` + AppData *v1.AppData `protobuf:"bytes,17,opt,name=app_data,json=appData,proto3" json:"app_data,omitempty"` +} + +func (x *SignupRequest) Reset() { + *x = SignupRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignupRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignupRequest) ProtoMessage() {} + +func (x *SignupRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignupRequest.ProtoReflect.Descriptor instead. +func (*SignupRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{0} +} + +func (x *SignupRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *SignupRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *SignupRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *SignupRequest) GetConfirmPassword() string { + if x != nil { + return x.ConfirmPassword + } + return "" +} + +func (x *SignupRequest) GetGivenName() string { + if x != nil { + return x.GivenName + } + return "" +} + +func (x *SignupRequest) GetFamilyName() string { + if x != nil { + return x.FamilyName + } + return "" +} + +func (x *SignupRequest) GetMiddleName() string { + if x != nil { + return x.MiddleName + } + return "" +} + +func (x *SignupRequest) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *SignupRequest) GetGender() string { + if x != nil { + return x.Gender + } + return "" +} + +func (x *SignupRequest) GetBirthdate() string { + if x != nil { + return x.Birthdate + } + return "" +} + +func (x *SignupRequest) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *SignupRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *SignupRequest) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + +func (x *SignupRequest) GetRedirectUri() string { + if x != nil { + return x.RedirectUri + } + return "" +} + +func (x *SignupRequest) GetIsMultiFactorAuthEnabled() bool { + if x != nil { + return x.IsMultiFactorAuthEnabled + } + return false +} + +func (x *SignupRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *SignupRequest) GetAppData() *v1.AppData { + if x != nil { + return x.AppData + } + return nil +} + +type SignupResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Auth *AuthResponse `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"` +} + +func (x *SignupResponse) Reset() { + *x = SignupResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignupResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignupResponse) ProtoMessage() {} + +func (x *SignupResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignupResponse.ProtoReflect.Descriptor instead. +func (*SignupResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{1} +} + +func (x *SignupResponse) GetAuth() *AuthResponse { + if x != nil { + return x.Auth + } + return nil +} + +type LoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + PhoneNumber string `protobuf:"bytes,2,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + Roles []string `protobuf:"bytes,4,rep,name=roles,proto3" json:"roles,omitempty"` + Scope []string `protobuf:"bytes,5,rep,name=scope,proto3" json:"scope,omitempty"` + State string `protobuf:"bytes,6,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *LoginRequest) Reset() { + *x = LoginRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[2] + 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_authorizer_v1_authorizer_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. +func (*LoginRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{2} +} + +func (x *LoginRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *LoginRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *LoginRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *LoginRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *LoginRequest) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + +func (x *LoginRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type LoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Auth *AuthResponse `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"` +} + +func (x *LoginResponse) Reset() { + *x = LoginResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginResponse) ProtoMessage() {} + +func (x *LoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. +func (*LoginResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{3} +} + +func (x *LoginResponse) GetAuth() *AuthResponse { + if x != nil { + return x.Auth + } + return nil +} + +type LogoutRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *LogoutRequest) Reset() { + *x = LogoutRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutRequest) ProtoMessage() {} + +func (x *LogoutRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. +func (*LogoutRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{4} +} + +type LogoutResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *LogoutResponse) Reset() { + *x = LogoutResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutResponse) ProtoMessage() {} + +func (x *LogoutResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. +func (*LogoutResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{5} +} + +func (x *LogoutResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type MagicLinkLoginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` + Scope []string `protobuf:"bytes,3,rep,name=scope,proto3" json:"scope,omitempty"` + State string `protobuf:"bytes,4,opt,name=state,proto3" json:"state,omitempty"` + RedirectUri string `protobuf:"bytes,5,opt,name=redirect_uri,json=redirectUri,proto3" json:"redirect_uri,omitempty"` +} + +func (x *MagicLinkLoginRequest) Reset() { + *x = MagicLinkLoginRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MagicLinkLoginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MagicLinkLoginRequest) ProtoMessage() {} + +func (x *MagicLinkLoginRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MagicLinkLoginRequest.ProtoReflect.Descriptor instead. +func (*MagicLinkLoginRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{6} +} + +func (x *MagicLinkLoginRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *MagicLinkLoginRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *MagicLinkLoginRequest) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + +func (x *MagicLinkLoginRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *MagicLinkLoginRequest) GetRedirectUri() string { + if x != nil { + return x.RedirectUri + } + return "" +} + +type MagicLinkLoginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *MagicLinkLoginResponse) Reset() { + *x = MagicLinkLoginResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MagicLinkLoginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MagicLinkLoginResponse) ProtoMessage() {} + +func (x *MagicLinkLoginResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MagicLinkLoginResponse.ProtoReflect.Descriptor instead. +func (*MagicLinkLoginResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{7} +} + +func (x *MagicLinkLoginResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type VerifyEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + State string `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *VerifyEmailRequest) Reset() { + *x = VerifyEmailRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VerifyEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyEmailRequest) ProtoMessage() {} + +func (x *VerifyEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyEmailRequest.ProtoReflect.Descriptor instead. +func (*VerifyEmailRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{8} +} + +func (x *VerifyEmailRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *VerifyEmailRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type VerifyEmailResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Auth *AuthResponse `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"` +} + +func (x *VerifyEmailResponse) Reset() { + *x = VerifyEmailResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VerifyEmailResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyEmailResponse) ProtoMessage() {} + +func (x *VerifyEmailResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyEmailResponse.ProtoReflect.Descriptor instead. +func (*VerifyEmailResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{9} +} + +func (x *VerifyEmailResponse) GetAuth() *AuthResponse { + if x != nil { + return x.Auth + } + return nil +} + +type ResendVerifyEmailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Identifier string `protobuf:"bytes,2,opt,name=identifier,proto3" json:"identifier,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *ResendVerifyEmailRequest) Reset() { + *x = ResendVerifyEmailRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResendVerifyEmailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResendVerifyEmailRequest) ProtoMessage() {} + +func (x *ResendVerifyEmailRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResendVerifyEmailRequest.ProtoReflect.Descriptor instead. +func (*ResendVerifyEmailRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{10} +} + +func (x *ResendVerifyEmailRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ResendVerifyEmailRequest) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *ResendVerifyEmailRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type ResendVerifyEmailResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *ResendVerifyEmailResponse) Reset() { + *x = ResendVerifyEmailResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResendVerifyEmailResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResendVerifyEmailResponse) ProtoMessage() {} + +func (x *ResendVerifyEmailResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResendVerifyEmailResponse.ProtoReflect.Descriptor instead. +func (*ResendVerifyEmailResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{11} +} + +func (x *ResendVerifyEmailResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type VerifyOtpRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Exactly one of email / phone_number is required. + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + PhoneNumber string `protobuf:"bytes,2,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + Otp string `protobuf:"bytes,3,opt,name=otp,proto3" json:"otp,omitempty"` + IsTotp bool `protobuf:"varint,4,opt,name=is_totp,json=isTotp,proto3" json:"is_totp,omitempty"` + State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *VerifyOtpRequest) Reset() { + *x = VerifyOtpRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VerifyOtpRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyOtpRequest) ProtoMessage() {} + +func (x *VerifyOtpRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyOtpRequest.ProtoReflect.Descriptor instead. +func (*VerifyOtpRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{12} +} + +func (x *VerifyOtpRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *VerifyOtpRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *VerifyOtpRequest) GetOtp() string { + if x != nil { + return x.Otp + } + return "" +} + +func (x *VerifyOtpRequest) GetIsTotp() bool { + if x != nil { + return x.IsTotp + } + return false +} + +func (x *VerifyOtpRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type VerifyOtpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Auth *AuthResponse `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"` +} + +func (x *VerifyOtpResponse) Reset() { + *x = VerifyOtpResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VerifyOtpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VerifyOtpResponse) ProtoMessage() {} + +func (x *VerifyOtpResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VerifyOtpResponse.ProtoReflect.Descriptor instead. +func (*VerifyOtpResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{13} +} + +func (x *VerifyOtpResponse) GetAuth() *AuthResponse { + if x != nil { + return x.Auth + } + return nil +} + +type ResendOtpRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + PhoneNumber string `protobuf:"bytes,2,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` +} + +func (x *ResendOtpRequest) Reset() { + *x = ResendOtpRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResendOtpRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResendOtpRequest) ProtoMessage() {} + +func (x *ResendOtpRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResendOtpRequest.ProtoReflect.Descriptor instead. +func (*ResendOtpRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{14} +} + +func (x *ResendOtpRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ResendOtpRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *ResendOtpRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +type ResendOtpResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *ResendOtpResponse) Reset() { + *x = ResendOtpResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResendOtpResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResendOtpResponse) ProtoMessage() {} + +func (x *ResendOtpResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResendOtpResponse.ProtoReflect.Descriptor instead. +func (*ResendOtpResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{15} +} + +func (x *ResendOtpResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ForgotPasswordRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + PhoneNumber string `protobuf:"bytes,2,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + RedirectUri string `protobuf:"bytes,4,opt,name=redirect_uri,json=redirectUri,proto3" json:"redirect_uri,omitempty"` +} + +func (x *ForgotPasswordRequest) Reset() { + *x = ForgotPasswordRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForgotPasswordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForgotPasswordRequest) ProtoMessage() {} + +func (x *ForgotPasswordRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForgotPasswordRequest.ProtoReflect.Descriptor instead. +func (*ForgotPasswordRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{16} +} + +func (x *ForgotPasswordRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ForgotPasswordRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *ForgotPasswordRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ForgotPasswordRequest) GetRedirectUri() string { + if x != nil { + return x.RedirectUri + } + return "" +} + +type ForgotPasswordResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + // For SMS-driven flows the UI may need to render an OTP entry screen. + ShouldShowMobileOtpScreen bool `protobuf:"varint,2,opt,name=should_show_mobile_otp_screen,json=shouldShowMobileOtpScreen,proto3" json:"should_show_mobile_otp_screen,omitempty"` +} + +func (x *ForgotPasswordResponse) Reset() { + *x = ForgotPasswordResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForgotPasswordResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForgotPasswordResponse) ProtoMessage() {} + +func (x *ForgotPasswordResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForgotPasswordResponse.ProtoReflect.Descriptor instead. +func (*ForgotPasswordResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{17} +} + +func (x *ForgotPasswordResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ForgotPasswordResponse) GetShouldShowMobileOtpScreen() bool { + if x != nil { + return x.ShouldShowMobileOtpScreen + } + return false +} + +type ResetPasswordRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // For email flows: the token from the reset email. For SMS flows: the OTP. + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Otp string `protobuf:"bytes,2,opt,name=otp,proto3" json:"otp,omitempty"` + PhoneNumber string `protobuf:"bytes,3,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` + ConfirmPassword string `protobuf:"bytes,5,opt,name=confirm_password,json=confirmPassword,proto3" json:"confirm_password,omitempty"` +} + +func (x *ResetPasswordRequest) Reset() { + *x = ResetPasswordRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResetPasswordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetPasswordRequest) ProtoMessage() {} + +func (x *ResetPasswordRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetPasswordRequest.ProtoReflect.Descriptor instead. +func (*ResetPasswordRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{18} +} + +func (x *ResetPasswordRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *ResetPasswordRequest) GetOtp() string { + if x != nil { + return x.Otp + } + return "" +} + +func (x *ResetPasswordRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *ResetPasswordRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *ResetPasswordRequest) GetConfirmPassword() string { + if x != nil { + return x.ConfirmPassword + } + return "" +} + +type ResetPasswordResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *ResetPasswordResponse) Reset() { + *x = ResetPasswordResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResetPasswordResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetPasswordResponse) ProtoMessage() {} + +func (x *ResetPasswordResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetPasswordResponse.ProtoReflect.Descriptor instead. +func (*ResetPasswordResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{19} +} + +func (x *ResetPasswordResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ProfileRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ProfileRequest) Reset() { + *x = ProfileRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProfileRequest) ProtoMessage() {} + +func (x *ProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProfileRequest.ProtoReflect.Descriptor instead. +func (*ProfileRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{20} +} + +type ProfileResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *ProfileResponse) Reset() { + *x = ProfileResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProfileResponse) ProtoMessage() {} + +func (x *ProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProfileResponse.ProtoReflect.Descriptor instead. +func (*ProfileResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{21} +} + +func (x *ProfileResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +type UpdateProfileRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OldPassword string `protobuf:"bytes,1,opt,name=old_password,json=oldPassword,proto3" json:"old_password,omitempty"` + NewPassword string `protobuf:"bytes,2,opt,name=new_password,json=newPassword,proto3" json:"new_password,omitempty"` + ConfirmNewPassword string `protobuf:"bytes,3,opt,name=confirm_new_password,json=confirmNewPassword,proto3" json:"confirm_new_password,omitempty"` + Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` + GivenName string `protobuf:"bytes,5,opt,name=given_name,json=givenName,proto3" json:"given_name,omitempty"` + FamilyName string `protobuf:"bytes,6,opt,name=family_name,json=familyName,proto3" json:"family_name,omitempty"` + MiddleName string `protobuf:"bytes,7,opt,name=middle_name,json=middleName,proto3" json:"middle_name,omitempty"` + Nickname string `protobuf:"bytes,8,opt,name=nickname,proto3" json:"nickname,omitempty"` + Gender string `protobuf:"bytes,9,opt,name=gender,proto3" json:"gender,omitempty"` + Birthdate string `protobuf:"bytes,10,opt,name=birthdate,proto3" json:"birthdate,omitempty"` + PhoneNumber string `protobuf:"bytes,11,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + Picture string `protobuf:"bytes,12,opt,name=picture,proto3" json:"picture,omitempty"` + IsMultiFactorAuthEnabled bool `protobuf:"varint,13,opt,name=is_multi_factor_auth_enabled,json=isMultiFactorAuthEnabled,proto3" json:"is_multi_factor_auth_enabled,omitempty"` + AppData *v1.AppData `protobuf:"bytes,14,opt,name=app_data,json=appData,proto3" json:"app_data,omitempty"` +} + +func (x *UpdateProfileRequest) Reset() { + *x = UpdateProfileRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProfileRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProfileRequest) ProtoMessage() {} + +func (x *UpdateProfileRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProfileRequest.ProtoReflect.Descriptor instead. +func (*UpdateProfileRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{22} +} + +func (x *UpdateProfileRequest) GetOldPassword() string { + if x != nil { + return x.OldPassword + } + return "" +} + +func (x *UpdateProfileRequest) GetNewPassword() string { + if x != nil { + return x.NewPassword + } + return "" +} + +func (x *UpdateProfileRequest) GetConfirmNewPassword() string { + if x != nil { + return x.ConfirmNewPassword + } + return "" +} + +func (x *UpdateProfileRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *UpdateProfileRequest) GetGivenName() string { + if x != nil { + return x.GivenName + } + return "" +} + +func (x *UpdateProfileRequest) GetFamilyName() string { + if x != nil { + return x.FamilyName + } + return "" +} + +func (x *UpdateProfileRequest) GetMiddleName() string { + if x != nil { + return x.MiddleName + } + return "" +} + +func (x *UpdateProfileRequest) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *UpdateProfileRequest) GetGender() string { + if x != nil { + return x.Gender + } + return "" +} + +func (x *UpdateProfileRequest) GetBirthdate() string { + if x != nil { + return x.Birthdate + } + return "" +} + +func (x *UpdateProfileRequest) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *UpdateProfileRequest) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *UpdateProfileRequest) GetIsMultiFactorAuthEnabled() bool { + if x != nil { + return x.IsMultiFactorAuthEnabled + } + return false +} + +func (x *UpdateProfileRequest) GetAppData() *v1.AppData { + if x != nil { + return x.AppData + } + return nil +} + +type UpdateProfileResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *UpdateProfileResponse) Reset() { + *x = UpdateProfileResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateProfileResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateProfileResponse) ProtoMessage() {} + +func (x *UpdateProfileResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateProfileResponse.ProtoReflect.Descriptor instead. +func (*UpdateProfileResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{23} +} + +func (x *UpdateProfileResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type DeactivateAccountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeactivateAccountRequest) Reset() { + *x = DeactivateAccountRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeactivateAccountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeactivateAccountRequest) ProtoMessage() {} + +func (x *DeactivateAccountRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeactivateAccountRequest.ProtoReflect.Descriptor instead. +func (*DeactivateAccountRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{24} +} + +type DeactivateAccountResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *DeactivateAccountResponse) Reset() { + *x = DeactivateAccountResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeactivateAccountResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeactivateAccountResponse) ProtoMessage() {} + +func (x *DeactivateAccountResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeactivateAccountResponse.ProtoReflect.Descriptor instead. +func (*DeactivateAccountResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{25} +} + +func (x *DeactivateAccountResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type RevokeRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` +} + +func (x *RevokeRequest) Reset() { + *x = RevokeRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeRequest) ProtoMessage() {} + +func (x *RevokeRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeRequest.ProtoReflect.Descriptor instead. +func (*RevokeRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{26} +} + +func (x *RevokeRequest) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +type RevokeResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *RevokeResponse) Reset() { + *x = RevokeResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeResponse) ProtoMessage() {} + +func (x *RevokeResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RevokeResponse.ProtoReflect.Descriptor instead. +func (*RevokeResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{27} +} + +func (x *RevokeResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type SessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Roles []string `protobuf:"bytes,1,rep,name=roles,proto3" json:"roles,omitempty"` + Scope []string `protobuf:"bytes,2,rep,name=scope,proto3" json:"scope,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + // Optional fine-grained authorization gate: each (relation, object) is + // checked against the authenticated caller with AND semantics, fail-closed. + // Requires fine-grained authorization enabled (--fga-store). + RequiredRelations []*FgaRelationInput `protobuf:"bytes,4,rep,name=required_relations,json=requiredRelations,proto3" json:"required_relations,omitempty"` +} + +func (x *SessionRequest) Reset() { + *x = SessionRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionRequest) ProtoMessage() {} + +func (x *SessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionRequest.ProtoReflect.Descriptor instead. +func (*SessionRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{28} +} + +func (x *SessionRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *SessionRequest) GetScope() []string { + if x != nil { + return x.Scope + } + return nil +} + +func (x *SessionRequest) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *SessionRequest) GetRequiredRelations() []*FgaRelationInput { + if x != nil { + return x.RequiredRelations + } + return nil +} + +type SessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Auth *AuthResponse `protobuf:"bytes,1,opt,name=auth,proto3" json:"auth,omitempty"` +} + +func (x *SessionResponse) Reset() { + *x = SessionResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionResponse) ProtoMessage() {} + +func (x *SessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionResponse.ProtoReflect.Descriptor instead. +func (*SessionResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{29} +} + +func (x *SessionResponse) GetAuth() *AuthResponse { + if x != nil { + return x.Auth + } + return nil +} + +type ValidateJwtTokenRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TokenType string `protobuf:"bytes,1,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + Roles []string `protobuf:"bytes,3,rep,name=roles,proto3" json:"roles,omitempty"` + // Optional fine-grained authorization gate (AND semantics, fail-closed). + RequiredRelations []*FgaRelationInput `protobuf:"bytes,4,rep,name=required_relations,json=requiredRelations,proto3" json:"required_relations,omitempty"` +} + +func (x *ValidateJwtTokenRequest) Reset() { + *x = ValidateJwtTokenRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateJwtTokenRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateJwtTokenRequest) ProtoMessage() {} + +func (x *ValidateJwtTokenRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateJwtTokenRequest.ProtoReflect.Descriptor instead. +func (*ValidateJwtTokenRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{30} +} + +func (x *ValidateJwtTokenRequest) GetTokenType() string { + if x != nil { + return x.TokenType + } + return "" +} + +func (x *ValidateJwtTokenRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *ValidateJwtTokenRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *ValidateJwtTokenRequest) GetRequiredRelations() []*FgaRelationInput { + if x != nil { + return x.RequiredRelations + } + return nil +} + +type ValidateJwtTokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"` + // Free-form JWT claims (matches GraphQL ValidateJWTTokenResponse.claims). + Claims *v1.AppData `protobuf:"bytes,2,opt,name=claims,proto3" json:"claims,omitempty"` +} + +func (x *ValidateJwtTokenResponse) Reset() { + *x = ValidateJwtTokenResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateJwtTokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateJwtTokenResponse) ProtoMessage() {} + +func (x *ValidateJwtTokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateJwtTokenResponse.ProtoReflect.Descriptor instead. +func (*ValidateJwtTokenResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{31} +} + +func (x *ValidateJwtTokenResponse) GetIsValid() bool { + if x != nil { + return x.IsValid + } + return false +} + +func (x *ValidateJwtTokenResponse) GetClaims() *v1.AppData { + if x != nil { + return x.Claims + } + return nil +} + +type ValidateSessionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Cookie string `protobuf:"bytes,1,opt,name=cookie,proto3" json:"cookie,omitempty"` + Roles []string `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` + // Optional fine-grained authorization gate (AND semantics, fail-closed). + RequiredRelations []*FgaRelationInput `protobuf:"bytes,3,rep,name=required_relations,json=requiredRelations,proto3" json:"required_relations,omitempty"` +} + +func (x *ValidateSessionRequest) Reset() { + *x = ValidateSessionRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateSessionRequest) ProtoMessage() {} + +func (x *ValidateSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateSessionRequest.ProtoReflect.Descriptor instead. +func (*ValidateSessionRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{32} +} + +func (x *ValidateSessionRequest) GetCookie() string { + if x != nil { + return x.Cookie + } + return "" +} + +func (x *ValidateSessionRequest) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *ValidateSessionRequest) GetRequiredRelations() []*FgaRelationInput { + if x != nil { + return x.RequiredRelations + } + return nil +} + +type ValidateSessionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + IsValid bool `protobuf:"varint,1,opt,name=is_valid,json=isValid,proto3" json:"is_valid,omitempty"` + User *User `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *ValidateSessionResponse) Reset() { + *x = ValidateSessionResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateSessionResponse) ProtoMessage() {} + +func (x *ValidateSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateSessionResponse.ProtoReflect.Descriptor instead. +func (*ValidateSessionResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{33} +} + +func (x *ValidateSessionResponse) GetIsValid() bool { + if x != nil { + return x.IsValid + } + return false +} + +func (x *ValidateSessionResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +type MetaRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *MetaRequest) Reset() { + *x = MetaRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MetaRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetaRequest) ProtoMessage() {} + +func (x *MetaRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MetaRequest.ProtoReflect.Descriptor instead. +func (*MetaRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{34} +} + +type MetaResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Meta *Meta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` +} + +func (x *MetaResponse) Reset() { + *x = MetaResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MetaResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MetaResponse) ProtoMessage() {} + +func (x *MetaResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MetaResponse.ProtoReflect.Descriptor instead. +func (*MetaResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{35} +} + +func (x *MetaResponse) GetMeta() *Meta { + if x != nil { + return x.Meta + } + return nil +} + +type CheckPermissionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The checks to evaluate; at least one, at most 100. + Checks []*PermissionCheckInput `protobuf:"bytes,1,rep,name=checks,proto3" json:"checks,omitempty"` + // Optional explicit subject ("type:id", or a bare id treated as + // "user:"). Honored only for super-admins or when it equals the + // caller's own token subject; anything else is rejected. + User string `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *CheckPermissionsRequest) Reset() { + *x = CheckPermissionsRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckPermissionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckPermissionsRequest) ProtoMessage() {} + +func (x *CheckPermissionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckPermissionsRequest.ProtoReflect.Descriptor instead. +func (*CheckPermissionsRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{36} +} + +func (x *CheckPermissionsRequest) GetChecks() []*PermissionCheckInput { + if x != nil { + return x.Checks + } + return nil +} + +func (x *CheckPermissionsRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +type CheckPermissionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // One result per supplied check, in order. + Results []*PermissionCheckResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` +} + +func (x *CheckPermissionsResponse) Reset() { + *x = CheckPermissionsResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CheckPermissionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckPermissionsResponse) ProtoMessage() {} + +func (x *CheckPermissionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckPermissionsResponse.ProtoReflect.Descriptor instead. +func (*CheckPermissionsResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{37} +} + +func (x *CheckPermissionsResponse) GetResults() []*PermissionCheckResult { + if x != nil { + return x.Results + } + return nil +} + +type ListPermissionsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Optional relation filter (e.g. "can_view"). + Relation string `protobuf:"bytes,1,opt,name=relation,proto3" json:"relation,omitempty"` + // Optional object-type filter (e.g. "document"). + ObjectType string `protobuf:"bytes,2,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"` + // Optional explicit subject; same trust rules as CheckPermissionsRequest. + User string `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` +} + +func (x *ListPermissionsRequest) Reset() { + *x = ListPermissionsRequest{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPermissionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPermissionsRequest) ProtoMessage() {} + +func (x *ListPermissionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPermissionsRequest.ProtoReflect.Descriptor instead. +func (*ListPermissionsRequest) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{38} +} + +func (x *ListPermissionsRequest) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *ListPermissionsRequest) GetObjectType() string { + if x != nil { + return x.ObjectType + } + return "" +} + +func (x *ListPermissionsRequest) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +type ListPermissionsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Distinct fully-qualified object ids the subject can access. + Objects []string `protobuf:"bytes,1,rep,name=objects,proto3" json:"objects,omitempty"` + // The (object, relation) detail — relevant when no relation filter was + // supplied. + Permissions []*Permission `protobuf:"bytes,2,rep,name=permissions,proto3" json:"permissions,omitempty"` + // True when the result was capped (1000 entries) and more permissions + // exist. + Truncated bool `protobuf:"varint,3,opt,name=truncated,proto3" json:"truncated,omitempty"` +} + +func (x *ListPermissionsResponse) Reset() { + *x = ListPermissionsResponse{} + mi := &file_authorizer_v1_authorizer_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPermissionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPermissionsResponse) ProtoMessage() {} + +func (x *ListPermissionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_authorizer_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPermissionsResponse.ProtoReflect.Descriptor instead. +func (*ListPermissionsResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_authorizer_proto_rawDescGZIP(), []int{39} +} + +func (x *ListPermissionsResponse) GetObjects() []string { + if x != nil { + return x.Objects + } + return nil +} + +func (x *ListPermissionsResponse) GetPermissions() []*Permission { + if x != nil { + return x.Permissions + } + return nil +} + +func (x *ListPermissionsResponse) GetTruncated() bool { + if x != nil { + return x.Truncated + } + return false +} + +var File_authorizer_v1_authorizer_proto protoreflect.FileDescriptor + +var file_authorizer_v1_authorizer_proto_rawDesc = []byte{ + 0x0a, 0x1e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, + 0x26, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, + 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xe6, 0x04, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x2a, 0x0a, 0x0c, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, + 0x52, 0x0b, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, + 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x35, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, + 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x0f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x72, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x67, 0x69, 0x76, 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, + 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, + 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x12, 0x3e, 0x0a, 0x1c, 0x69, 0x73, 0x5f, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, + 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, + 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x75, 0x74, + 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x38, + 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x44, 0x61, 0x74, 0x61, 0x52, + 0x07, 0x61, 0x70, 0x70, 0x44, 0x61, 0x74, 0x61, 0x22, 0x41, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, + 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x61, 0x75, + 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xc4, 0x01, 0x0a, 0x0c, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, + 0x72, 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2a, 0x0a, 0x0c, + 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, 0x0b, 0x70, 0x68, 0x6f, + 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, + 0x05, 0x10, 0x01, 0x18, 0x80, 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x22, 0x40, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x04, + 0x61, 0x75, 0x74, 0x68, 0x22, 0x0f, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2a, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, + 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x72, + 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, + 0x22, 0x32, 0x0a, 0x16, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x22, 0x49, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, + 0x10, 0x01, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, + 0x46, 0x0a, 0x13, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x79, 0x0a, 0x18, 0x52, 0x65, 0x73, 0x65, 0x6e, + 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x12, 0x27, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x22, 0x35, 0x0a, 0x19, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xaa, 0x01, 0x0a, 0x10, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, + 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, + 0x48, 0x05, 0x72, 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2a, + 0x0a, 0x0c, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, 0x0b, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x03, 0x6f, 0x74, + 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xba, 0x48, 0x06, 0x72, 0x04, 0x10, 0x01, + 0x18, 0x10, 0x52, 0x03, 0x6f, 0x74, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x74, 0x6f, + 0x74, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x54, 0x6f, 0x74, 0x70, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x44, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, + 0x4f, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x61, + 0x75, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0x74, 0x0a, 0x10, + 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1e, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x2a, 0x0a, 0x0c, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, + 0x0b, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x22, 0x2d, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x74, 0x70, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x46, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1e, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, + 0x03, 0x18, 0xc0, 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x2a, 0x0a, 0x0c, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, 0x0b, 0x70, 0x68, 0x6f, 0x6e, + 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, + 0x22, 0x74, 0x0a, 0x16, 0x46, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x73, + 0x68, 0x6f, 0x77, 0x5f, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x5f, 0x6f, 0x74, 0x70, 0x5f, 0x73, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x53, 0x68, 0x6f, 0x77, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x4f, 0x74, 0x70, + 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, 0xc9, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x65, 0x74, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6f, 0x74, 0x70, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6f, 0x74, 0x70, 0x12, 0x2a, 0x0a, 0x0c, 0x70, 0x68, 0x6f, 0x6e, 0x65, + 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, + 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, 0x0b, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, + 0x01, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x35, 0x0a, 0x10, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, + 0x01, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x22, 0x31, 0x0a, 0x15, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x10, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, + 0x73, 0x65, 0x72, 0x22, 0xb5, 0x04, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, + 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x6f, 0x6c, 0x64, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x6c, 0x64, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, + 0x2b, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0x80, 0x01, 0x52, + 0x0b, 0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x3a, 0x0a, 0x14, + 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, + 0x03, 0x18, 0x80, 0x01, 0x52, 0x12, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x4e, 0x65, 0x77, + 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1e, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xba, 0x48, 0x05, 0x72, 0x03, 0x18, 0xc0, + 0x02, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x76, 0x65, + 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x69, + 0x76, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x6d, 0x69, 0x6c, + 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x61, + 0x6d, 0x69, 0x6c, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x64, 0x64, + 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, + 0x69, 0x64, 0x64, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, + 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, + 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, + 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2a, 0x0a, 0x0c, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x18, 0x20, 0x52, 0x0b, 0x70, 0x68, 0x6f, 0x6e, + 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, + 0x65, 0x12, 0x3e, 0x0a, 0x1c, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x66, 0x61, + 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, + 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x38, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x07, 0x61, 0x70, 0x70, 0x44, 0x61, 0x74, 0x61, 0x22, 0x31, 0x0a, 0x15, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x1a, + 0x0a, 0x18, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x35, 0x0a, 0x19, 0x44, 0x65, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x22, 0x3d, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, + 0x10, 0x01, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x22, 0x2a, 0x0a, 0x0e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xa2, 0x01, 0x0a, + 0x0e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x67, + 0x61, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x11, + 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x42, 0x0a, 0x0f, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, + 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xc6, 0x01, 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x4a, 0x77, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x26, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, 0x52, 0x09, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x05, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, + 0x01, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x4e, + 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x67, 0x61, 0x52, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x11, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x6c, + 0x0a, 0x18, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x77, 0x74, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, + 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, + 0x44, 0x61, 0x74, 0x61, 0x52, 0x06, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x22, 0x9f, 0x01, 0x0a, + 0x16, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x07, 0xba, 0x48, 0x04, 0x72, 0x02, 0x10, 0x01, + 0x52, 0x06, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x6c, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x12, 0x4e, + 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x67, 0x61, 0x52, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x11, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5d, + 0x0a, 0x17, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, + 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x56, + 0x61, 0x6c, 0x69, 0x64, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x0d, 0x0a, + 0x0b, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x37, 0x0a, 0x0c, + 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, + 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x76, 0x0a, 0x17, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x47, 0x0a, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x49, 0x6e, 0x70, 0x75, 0x74, 0x42, 0x0a, 0xba, 0x48, 0x07, 0x92, 0x01, 0x04, 0x08, 0x01, 0x10, + 0x64, 0x52, 0x06, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x5a, 0x0a, + 0x18, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x75, 0x73, 0x65, 0x72, 0x22, 0x8e, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, + 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x07, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x0b, 0x70, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x75, 0x6e, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x74, 0x72, 0x75, 0x6e, + 0x63, 0x61, 0x74, 0x65, 0x64, 0x32, 0x89, 0x13, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x68, 0x0a, 0x06, 0x53, + 0x69, 0x67, 0x6e, 0x75, 0x70, 0x12, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x21, 0x92, 0xb5, 0x18, 0x00, 0x98, 0xb5, 0x18, 0x01, 0xa0, 0xb5, 0x18, 0x01, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x69, 0x67, 0x6e, 0x75, 0x70, 0x12, 0x64, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1b, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x92, 0xb5, 0x18, 0x00, 0x98, + 0xb5, 0x18, 0x01, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x3a, 0x01, 0x2a, + 0x22, 0x09, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x5d, 0x0a, 0x06, 0x4c, + 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x16, 0x98, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x22, 0x0a, + 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x86, 0x01, 0x0a, 0x0e, 0x4d, + 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x24, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, + 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x27, 0x98, 0xb5, 0x18, 0x01, + 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, + 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x12, 0x79, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, + 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x98, 0xb5, 0x18, 0x01, 0xa0, + 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x76, + 0x31, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x8e, + 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, + 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x27, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, + 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x65, 0x6e, 0x64, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x64, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, + 0x71, 0x0a, 0x09, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4f, 0x74, 0x70, 0x12, 0x1f, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x21, 0x98, 0xb5, 0x18, 0x01, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, + 0x01, 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x6f, + 0x74, 0x70, 0x12, 0x6d, 0x0a, 0x09, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x74, 0x70, 0x12, + 0x1f, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x4f, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x1d, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x3a, 0x01, + 0x2a, 0x22, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x74, + 0x70, 0x12, 0x81, 0x01, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x50, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x12, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x6f, + 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x22, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, + 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x5f, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x81, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x74, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x25, 0x98, 0xb5, 0x18, 0x01, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x65, 0x74, + 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x63, 0x0a, 0x07, 0x50, 0x72, 0x6f, + 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x19, 0x92, 0xb5, 0x18, 0x02, 0x08, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x0d, 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x7d, + 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, + 0x23, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x21, 0x98, 0xb5, 0x18, 0x01, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x93, 0x01, + 0x0a, 0x11, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x27, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x63, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, + 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2b, 0x92, 0xb5, 0x18, 0x02, 0x18, 0x01, 0x98, 0xb5, + 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x76, 0x31, + 0x2f, 0x64, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x60, 0x0a, 0x06, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x12, 0x1c, 0x2e, + 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x6f, + 0x6b, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x98, 0xb5, 0x18, 0x01, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x72, + 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x12, 0x60, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x8a, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x4a, 0x77, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x26, 0x2e, 0x61, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x77, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x77, 0x74, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0xa0, + 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, 0x2f, 0x76, + 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6a, 0x77, 0x74, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x85, 0x01, 0x0a, 0x0f, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x26, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x5b, 0x0a, 0x04, + 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x92, + 0xb5, 0x18, 0x02, 0x08, 0x01, 0xa0, 0xb5, 0x18, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, + 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x8b, 0x01, 0x0a, 0x10, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, + 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x26, 0x92, 0xb5, 0x18, 0x02, 0x08, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, + 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, + 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x92, 0xb5, 0x18, 0x02, + 0x08, 0x01, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, + 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x42, 0xc0, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x42, 0x0f, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x64, 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, + 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, + 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x76, + 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, + 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_authorizer_v1_authorizer_proto_rawDescOnce sync.Once + file_authorizer_v1_authorizer_proto_rawDescData = file_authorizer_v1_authorizer_proto_rawDesc +) + +func file_authorizer_v1_authorizer_proto_rawDescGZIP() []byte { + file_authorizer_v1_authorizer_proto_rawDescOnce.Do(func() { + file_authorizer_v1_authorizer_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_v1_authorizer_proto_rawDescData) + }) + return file_authorizer_v1_authorizer_proto_rawDescData +} + +var file_authorizer_v1_authorizer_proto_msgTypes = make([]protoimpl.MessageInfo, 40) +var file_authorizer_v1_authorizer_proto_goTypes = []any{ + (*SignupRequest)(nil), // 0: authorizer.v1.SignupRequest + (*SignupResponse)(nil), // 1: authorizer.v1.SignupResponse + (*LoginRequest)(nil), // 2: authorizer.v1.LoginRequest + (*LoginResponse)(nil), // 3: authorizer.v1.LoginResponse + (*LogoutRequest)(nil), // 4: authorizer.v1.LogoutRequest + (*LogoutResponse)(nil), // 5: authorizer.v1.LogoutResponse + (*MagicLinkLoginRequest)(nil), // 6: authorizer.v1.MagicLinkLoginRequest + (*MagicLinkLoginResponse)(nil), // 7: authorizer.v1.MagicLinkLoginResponse + (*VerifyEmailRequest)(nil), // 8: authorizer.v1.VerifyEmailRequest + (*VerifyEmailResponse)(nil), // 9: authorizer.v1.VerifyEmailResponse + (*ResendVerifyEmailRequest)(nil), // 10: authorizer.v1.ResendVerifyEmailRequest + (*ResendVerifyEmailResponse)(nil), // 11: authorizer.v1.ResendVerifyEmailResponse + (*VerifyOtpRequest)(nil), // 12: authorizer.v1.VerifyOtpRequest + (*VerifyOtpResponse)(nil), // 13: authorizer.v1.VerifyOtpResponse + (*ResendOtpRequest)(nil), // 14: authorizer.v1.ResendOtpRequest + (*ResendOtpResponse)(nil), // 15: authorizer.v1.ResendOtpResponse + (*ForgotPasswordRequest)(nil), // 16: authorizer.v1.ForgotPasswordRequest + (*ForgotPasswordResponse)(nil), // 17: authorizer.v1.ForgotPasswordResponse + (*ResetPasswordRequest)(nil), // 18: authorizer.v1.ResetPasswordRequest + (*ResetPasswordResponse)(nil), // 19: authorizer.v1.ResetPasswordResponse + (*ProfileRequest)(nil), // 20: authorizer.v1.ProfileRequest + (*ProfileResponse)(nil), // 21: authorizer.v1.ProfileResponse + (*UpdateProfileRequest)(nil), // 22: authorizer.v1.UpdateProfileRequest + (*UpdateProfileResponse)(nil), // 23: authorizer.v1.UpdateProfileResponse + (*DeactivateAccountRequest)(nil), // 24: authorizer.v1.DeactivateAccountRequest + (*DeactivateAccountResponse)(nil), // 25: authorizer.v1.DeactivateAccountResponse + (*RevokeRequest)(nil), // 26: authorizer.v1.RevokeRequest + (*RevokeResponse)(nil), // 27: authorizer.v1.RevokeResponse + (*SessionRequest)(nil), // 28: authorizer.v1.SessionRequest + (*SessionResponse)(nil), // 29: authorizer.v1.SessionResponse + (*ValidateJwtTokenRequest)(nil), // 30: authorizer.v1.ValidateJwtTokenRequest + (*ValidateJwtTokenResponse)(nil), // 31: authorizer.v1.ValidateJwtTokenResponse + (*ValidateSessionRequest)(nil), // 32: authorizer.v1.ValidateSessionRequest + (*ValidateSessionResponse)(nil), // 33: authorizer.v1.ValidateSessionResponse + (*MetaRequest)(nil), // 34: authorizer.v1.MetaRequest + (*MetaResponse)(nil), // 35: authorizer.v1.MetaResponse + (*CheckPermissionsRequest)(nil), // 36: authorizer.v1.CheckPermissionsRequest + (*CheckPermissionsResponse)(nil), // 37: authorizer.v1.CheckPermissionsResponse + (*ListPermissionsRequest)(nil), // 38: authorizer.v1.ListPermissionsRequest + (*ListPermissionsResponse)(nil), // 39: authorizer.v1.ListPermissionsResponse + (*v1.AppData)(nil), // 40: authorizer.common.v1.AppData + (*AuthResponse)(nil), // 41: authorizer.v1.AuthResponse + (*User)(nil), // 42: authorizer.v1.User + (*FgaRelationInput)(nil), // 43: authorizer.v1.FgaRelationInput + (*Meta)(nil), // 44: authorizer.v1.Meta + (*PermissionCheckInput)(nil), // 45: authorizer.v1.PermissionCheckInput + (*PermissionCheckResult)(nil), // 46: authorizer.v1.PermissionCheckResult + (*Permission)(nil), // 47: authorizer.v1.Permission +} +var file_authorizer_v1_authorizer_proto_depIdxs = []int32{ + 40, // 0: authorizer.v1.SignupRequest.app_data:type_name -> authorizer.common.v1.AppData + 41, // 1: authorizer.v1.SignupResponse.auth:type_name -> authorizer.v1.AuthResponse + 41, // 2: authorizer.v1.LoginResponse.auth:type_name -> authorizer.v1.AuthResponse + 41, // 3: authorizer.v1.VerifyEmailResponse.auth:type_name -> authorizer.v1.AuthResponse + 41, // 4: authorizer.v1.VerifyOtpResponse.auth:type_name -> authorizer.v1.AuthResponse + 42, // 5: authorizer.v1.ProfileResponse.user:type_name -> authorizer.v1.User + 40, // 6: authorizer.v1.UpdateProfileRequest.app_data:type_name -> authorizer.common.v1.AppData + 43, // 7: authorizer.v1.SessionRequest.required_relations:type_name -> authorizer.v1.FgaRelationInput + 41, // 8: authorizer.v1.SessionResponse.auth:type_name -> authorizer.v1.AuthResponse + 43, // 9: authorizer.v1.ValidateJwtTokenRequest.required_relations:type_name -> authorizer.v1.FgaRelationInput + 40, // 10: authorizer.v1.ValidateJwtTokenResponse.claims:type_name -> authorizer.common.v1.AppData + 43, // 11: authorizer.v1.ValidateSessionRequest.required_relations:type_name -> authorizer.v1.FgaRelationInput + 42, // 12: authorizer.v1.ValidateSessionResponse.user:type_name -> authorizer.v1.User + 44, // 13: authorizer.v1.MetaResponse.meta:type_name -> authorizer.v1.Meta + 45, // 14: authorizer.v1.CheckPermissionsRequest.checks:type_name -> authorizer.v1.PermissionCheckInput + 46, // 15: authorizer.v1.CheckPermissionsResponse.results:type_name -> authorizer.v1.PermissionCheckResult + 47, // 16: authorizer.v1.ListPermissionsResponse.permissions:type_name -> authorizer.v1.Permission + 0, // 17: authorizer.v1.AuthorizerService.Signup:input_type -> authorizer.v1.SignupRequest + 2, // 18: authorizer.v1.AuthorizerService.Login:input_type -> authorizer.v1.LoginRequest + 4, // 19: authorizer.v1.AuthorizerService.Logout:input_type -> authorizer.v1.LogoutRequest + 6, // 20: authorizer.v1.AuthorizerService.MagicLinkLogin:input_type -> authorizer.v1.MagicLinkLoginRequest + 8, // 21: authorizer.v1.AuthorizerService.VerifyEmail:input_type -> authorizer.v1.VerifyEmailRequest + 10, // 22: authorizer.v1.AuthorizerService.ResendVerifyEmail:input_type -> authorizer.v1.ResendVerifyEmailRequest + 12, // 23: authorizer.v1.AuthorizerService.VerifyOtp:input_type -> authorizer.v1.VerifyOtpRequest + 14, // 24: authorizer.v1.AuthorizerService.ResendOtp:input_type -> authorizer.v1.ResendOtpRequest + 16, // 25: authorizer.v1.AuthorizerService.ForgotPassword:input_type -> authorizer.v1.ForgotPasswordRequest + 18, // 26: authorizer.v1.AuthorizerService.ResetPassword:input_type -> authorizer.v1.ResetPasswordRequest + 20, // 27: authorizer.v1.AuthorizerService.Profile:input_type -> authorizer.v1.ProfileRequest + 22, // 28: authorizer.v1.AuthorizerService.UpdateProfile:input_type -> authorizer.v1.UpdateProfileRequest + 24, // 29: authorizer.v1.AuthorizerService.DeactivateAccount:input_type -> authorizer.v1.DeactivateAccountRequest + 26, // 30: authorizer.v1.AuthorizerService.Revoke:input_type -> authorizer.v1.RevokeRequest + 28, // 31: authorizer.v1.AuthorizerService.Session:input_type -> authorizer.v1.SessionRequest + 30, // 32: authorizer.v1.AuthorizerService.ValidateJwtToken:input_type -> authorizer.v1.ValidateJwtTokenRequest + 32, // 33: authorizer.v1.AuthorizerService.ValidateSession:input_type -> authorizer.v1.ValidateSessionRequest + 34, // 34: authorizer.v1.AuthorizerService.Meta:input_type -> authorizer.v1.MetaRequest + 36, // 35: authorizer.v1.AuthorizerService.CheckPermissions:input_type -> authorizer.v1.CheckPermissionsRequest + 38, // 36: authorizer.v1.AuthorizerService.ListPermissions:input_type -> authorizer.v1.ListPermissionsRequest + 1, // 37: authorizer.v1.AuthorizerService.Signup:output_type -> authorizer.v1.SignupResponse + 3, // 38: authorizer.v1.AuthorizerService.Login:output_type -> authorizer.v1.LoginResponse + 5, // 39: authorizer.v1.AuthorizerService.Logout:output_type -> authorizer.v1.LogoutResponse + 7, // 40: authorizer.v1.AuthorizerService.MagicLinkLogin:output_type -> authorizer.v1.MagicLinkLoginResponse + 9, // 41: authorizer.v1.AuthorizerService.VerifyEmail:output_type -> authorizer.v1.VerifyEmailResponse + 11, // 42: authorizer.v1.AuthorizerService.ResendVerifyEmail:output_type -> authorizer.v1.ResendVerifyEmailResponse + 13, // 43: authorizer.v1.AuthorizerService.VerifyOtp:output_type -> authorizer.v1.VerifyOtpResponse + 15, // 44: authorizer.v1.AuthorizerService.ResendOtp:output_type -> authorizer.v1.ResendOtpResponse + 17, // 45: authorizer.v1.AuthorizerService.ForgotPassword:output_type -> authorizer.v1.ForgotPasswordResponse + 19, // 46: authorizer.v1.AuthorizerService.ResetPassword:output_type -> authorizer.v1.ResetPasswordResponse + 21, // 47: authorizer.v1.AuthorizerService.Profile:output_type -> authorizer.v1.ProfileResponse + 23, // 48: authorizer.v1.AuthorizerService.UpdateProfile:output_type -> authorizer.v1.UpdateProfileResponse + 25, // 49: authorizer.v1.AuthorizerService.DeactivateAccount:output_type -> authorizer.v1.DeactivateAccountResponse + 27, // 50: authorizer.v1.AuthorizerService.Revoke:output_type -> authorizer.v1.RevokeResponse + 29, // 51: authorizer.v1.AuthorizerService.Session:output_type -> authorizer.v1.SessionResponse + 31, // 52: authorizer.v1.AuthorizerService.ValidateJwtToken:output_type -> authorizer.v1.ValidateJwtTokenResponse + 33, // 53: authorizer.v1.AuthorizerService.ValidateSession:output_type -> authorizer.v1.ValidateSessionResponse + 35, // 54: authorizer.v1.AuthorizerService.Meta:output_type -> authorizer.v1.MetaResponse + 37, // 55: authorizer.v1.AuthorizerService.CheckPermissions:output_type -> authorizer.v1.CheckPermissionsResponse + 39, // 56: authorizer.v1.AuthorizerService.ListPermissions:output_type -> authorizer.v1.ListPermissionsResponse + 37, // [37:57] is the sub-list for method output_type + 17, // [17:37] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_authorizer_v1_authorizer_proto_init() } +func file_authorizer_v1_authorizer_proto_init() { + if File_authorizer_v1_authorizer_proto != nil { + return + } + file_authorizer_v1_types_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_v1_authorizer_proto_rawDesc, + NumEnums: 0, + NumMessages: 40, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_authorizer_v1_authorizer_proto_goTypes, + DependencyIndexes: file_authorizer_v1_authorizer_proto_depIdxs, + MessageInfos: file_authorizer_v1_authorizer_proto_msgTypes, + }.Build() + File_authorizer_v1_authorizer_proto = out.File + file_authorizer_v1_authorizer_proto_rawDesc = nil + file_authorizer_v1_authorizer_proto_goTypes = nil + file_authorizer_v1_authorizer_proto_depIdxs = nil +} diff --git a/gen/go/authorizer/v1/authorizer.pb.gw.go b/gen/go/authorizer/v1/authorizer.pb.gw.go new file mode 100644 index 00000000..17438927 --- /dev/null +++ b/gen/go/authorizer/v1/authorizer.pb.gw.go @@ -0,0 +1,1603 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: authorizer/v1/authorizer.proto + +/* +Package authorizerv1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package authorizerv1 + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_AuthorizerService_Signup_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SignupRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Signup(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Signup_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SignupRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Signup(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Login_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq LoginRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Login(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Login_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq LoginRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Login(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Logout_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq LogoutRequest + var metadata runtime.ServerMetadata + + msg, err := client.Logout(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Logout_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq LogoutRequest + var metadata runtime.ServerMetadata + + msg, err := server.Logout(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_MagicLinkLogin_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MagicLinkLoginRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.MagicLinkLogin(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_MagicLinkLogin_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MagicLinkLoginRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.MagicLinkLogin(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_VerifyEmail_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq VerifyEmailRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.VerifyEmail(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_VerifyEmail_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq VerifyEmailRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.VerifyEmail(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ResendVerifyEmail_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResendVerifyEmailRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ResendVerifyEmail(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ResendVerifyEmail_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResendVerifyEmailRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ResendVerifyEmail(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_VerifyOtp_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq VerifyOtpRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.VerifyOtp(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_VerifyOtp_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq VerifyOtpRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.VerifyOtp(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ResendOtp_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResendOtpRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ResendOtp(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ResendOtp_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResendOtpRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ResendOtp(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ForgotPassword_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ForgotPasswordRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ForgotPassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ForgotPassword_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ForgotPasswordRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ForgotPassword(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ResetPassword_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResetPasswordRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ResetPassword(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ResetPassword_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ResetPasswordRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ResetPassword(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Profile_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ProfileRequest + var metadata runtime.ServerMetadata + + msg, err := client.Profile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Profile_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ProfileRequest + var metadata runtime.ServerMetadata + + msg, err := server.Profile(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_UpdateProfile_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateProfileRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateProfile(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_UpdateProfile_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateProfileRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateProfile(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_DeactivateAccount_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeactivateAccountRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.DeactivateAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_DeactivateAccount_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeactivateAccountRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.DeactivateAccount(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Revoke_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RevokeRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Revoke(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Revoke_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RevokeRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Revoke(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Session_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SessionRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Session(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Session_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SessionRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Session(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ValidateJwtToken_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ValidateJwtTokenRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ValidateJwtToken(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ValidateJwtToken_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ValidateJwtTokenRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ValidateJwtToken(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ValidateSession_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ValidateSessionRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ValidateSession(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ValidateSession_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ValidateSessionRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ValidateSession(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_Meta_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MetaRequest + var metadata runtime.ServerMetadata + + msg, err := client.Meta(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_Meta_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MetaRequest + var metadata runtime.ServerMetadata + + msg, err := server.Meta(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_CheckPermissions_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CheckPermissionsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CheckPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_CheckPermissions_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CheckPermissionsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CheckPermissions(ctx, &protoReq) + return msg, metadata, err + +} + +func request_AuthorizerService_ListPermissions_0(ctx context.Context, marshaler runtime.Marshaler, client AuthorizerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListPermissionsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListPermissions(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_AuthorizerService_ListPermissions_0(ctx context.Context, marshaler runtime.Marshaler, server AuthorizerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListPermissionsRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListPermissions(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterAuthorizerServiceHandlerServer registers the http handlers for service AuthorizerService to "mux". +// UnaryRPC :call AuthorizerServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterAuthorizerServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterAuthorizerServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server AuthorizerServiceServer) error { + + mux.Handle("POST", pattern_AuthorizerService_Signup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Signup", runtime.WithHTTPPathPattern("/v1/signup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Signup_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Signup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Login", runtime.WithHTTPPathPattern("/v1/login")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Login_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Login_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Logout_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Logout", runtime.WithHTTPPathPattern("/v1/logout")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Logout_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Logout_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_MagicLinkLogin_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/MagicLinkLogin", runtime.WithHTTPPathPattern("/v1/magic_link_login")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_MagicLinkLogin_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_MagicLinkLogin_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_VerifyEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/VerifyEmail", runtime.WithHTTPPathPattern("/v1/verify_email")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_VerifyEmail_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_VerifyEmail_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResendVerifyEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResendVerifyEmail", runtime.WithHTTPPathPattern("/v1/resend_verify_email")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ResendVerifyEmail_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResendVerifyEmail_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_VerifyOtp_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/VerifyOtp", runtime.WithHTTPPathPattern("/v1/verify_otp")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_VerifyOtp_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_VerifyOtp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResendOtp_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResendOtp", runtime.WithHTTPPathPattern("/v1/resend_otp")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ResendOtp_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResendOtp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ForgotPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ForgotPassword", runtime.WithHTTPPathPattern("/v1/forgot_password")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ForgotPassword_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ForgotPassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResetPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResetPassword", runtime.WithHTTPPathPattern("/v1/reset_password")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ResetPassword_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResetPassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthorizerService_Profile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Profile", runtime.WithHTTPPathPattern("/v1/profile")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Profile_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Profile_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_UpdateProfile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/UpdateProfile", runtime.WithHTTPPathPattern("/v1/update_profile")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_UpdateProfile_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_UpdateProfile_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_DeactivateAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/DeactivateAccount", runtime.WithHTTPPathPattern("/v1/deactivate_account")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_DeactivateAccount_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_DeactivateAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Revoke_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Revoke", runtime.WithHTTPPathPattern("/v1/revoke")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Revoke_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Revoke_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Session_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Session", runtime.WithHTTPPathPattern("/v1/session")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Session_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Session_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ValidateJwtToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ValidateJwtToken", runtime.WithHTTPPathPattern("/v1/validate_jwt_token")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ValidateJwtToken_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ValidateJwtToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ValidateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ValidateSession", runtime.WithHTTPPathPattern("/v1/validate_session")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ValidateSession_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ValidateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthorizerService_Meta_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Meta", runtime.WithHTTPPathPattern("/v1/meta")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_Meta_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Meta_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_CheckPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/CheckPermissions", runtime.WithHTTPPathPattern("/v1/check_permissions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_CheckPermissions_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_CheckPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ListPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ListPermissions", runtime.WithHTTPPathPattern("/v1/list_permissions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_AuthorizerService_ListPermissions_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ListPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterAuthorizerServiceHandlerFromEndpoint is same as RegisterAuthorizerServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterAuthorizerServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterAuthorizerServiceHandler(ctx, mux, conn) +} + +// RegisterAuthorizerServiceHandler registers the http handlers for service AuthorizerService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterAuthorizerServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterAuthorizerServiceHandlerClient(ctx, mux, NewAuthorizerServiceClient(conn)) +} + +// RegisterAuthorizerServiceHandlerClient registers the http handlers for service AuthorizerService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "AuthorizerServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "AuthorizerServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "AuthorizerServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterAuthorizerServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client AuthorizerServiceClient) error { + + mux.Handle("POST", pattern_AuthorizerService_Signup_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Signup", runtime.WithHTTPPathPattern("/v1/signup")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Signup_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Signup_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Login_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Login", runtime.WithHTTPPathPattern("/v1/login")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Login_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Login_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Logout_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Logout", runtime.WithHTTPPathPattern("/v1/logout")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Logout_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Logout_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_MagicLinkLogin_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/MagicLinkLogin", runtime.WithHTTPPathPattern("/v1/magic_link_login")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_MagicLinkLogin_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_MagicLinkLogin_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_VerifyEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/VerifyEmail", runtime.WithHTTPPathPattern("/v1/verify_email")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_VerifyEmail_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_VerifyEmail_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResendVerifyEmail_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResendVerifyEmail", runtime.WithHTTPPathPattern("/v1/resend_verify_email")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ResendVerifyEmail_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResendVerifyEmail_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_VerifyOtp_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/VerifyOtp", runtime.WithHTTPPathPattern("/v1/verify_otp")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_VerifyOtp_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_VerifyOtp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResendOtp_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResendOtp", runtime.WithHTTPPathPattern("/v1/resend_otp")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ResendOtp_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResendOtp_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ForgotPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ForgotPassword", runtime.WithHTTPPathPattern("/v1/forgot_password")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ForgotPassword_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ForgotPassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ResetPassword_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ResetPassword", runtime.WithHTTPPathPattern("/v1/reset_password")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ResetPassword_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ResetPassword_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthorizerService_Profile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Profile", runtime.WithHTTPPathPattern("/v1/profile")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Profile_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Profile_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_UpdateProfile_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/UpdateProfile", runtime.WithHTTPPathPattern("/v1/update_profile")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_UpdateProfile_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_UpdateProfile_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_DeactivateAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/DeactivateAccount", runtime.WithHTTPPathPattern("/v1/deactivate_account")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_DeactivateAccount_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_DeactivateAccount_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Revoke_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Revoke", runtime.WithHTTPPathPattern("/v1/revoke")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Revoke_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Revoke_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_Session_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Session", runtime.WithHTTPPathPattern("/v1/session")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Session_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Session_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ValidateJwtToken_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ValidateJwtToken", runtime.WithHTTPPathPattern("/v1/validate_jwt_token")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ValidateJwtToken_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ValidateJwtToken_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ValidateSession_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ValidateSession", runtime.WithHTTPPathPattern("/v1/validate_session")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ValidateSession_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ValidateSession_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_AuthorizerService_Meta_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/Meta", runtime.WithHTTPPathPattern("/v1/meta")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_Meta_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_Meta_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_CheckPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/CheckPermissions", runtime.WithHTTPPathPattern("/v1/check_permissions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_CheckPermissions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_CheckPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_AuthorizerService_ListPermissions_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/authorizer.v1.AuthorizerService/ListPermissions", runtime.WithHTTPPathPattern("/v1/list_permissions")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_AuthorizerService_ListPermissions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_AuthorizerService_ListPermissions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_AuthorizerService_Signup_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "signup"}, "")) + + pattern_AuthorizerService_Login_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "login"}, "")) + + pattern_AuthorizerService_Logout_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "logout"}, "")) + + pattern_AuthorizerService_MagicLinkLogin_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "magic_link_login"}, "")) + + pattern_AuthorizerService_VerifyEmail_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "verify_email"}, "")) + + pattern_AuthorizerService_ResendVerifyEmail_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resend_verify_email"}, "")) + + pattern_AuthorizerService_VerifyOtp_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "verify_otp"}, "")) + + pattern_AuthorizerService_ResendOtp_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resend_otp"}, "")) + + pattern_AuthorizerService_ForgotPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "forgot_password"}, "")) + + pattern_AuthorizerService_ResetPassword_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "reset_password"}, "")) + + pattern_AuthorizerService_Profile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "profile"}, "")) + + pattern_AuthorizerService_UpdateProfile_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "update_profile"}, "")) + + pattern_AuthorizerService_DeactivateAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "deactivate_account"}, "")) + + pattern_AuthorizerService_Revoke_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "revoke"}, "")) + + pattern_AuthorizerService_Session_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "session"}, "")) + + pattern_AuthorizerService_ValidateJwtToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "validate_jwt_token"}, "")) + + pattern_AuthorizerService_ValidateSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "validate_session"}, "")) + + pattern_AuthorizerService_Meta_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "meta"}, "")) + + pattern_AuthorizerService_CheckPermissions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "check_permissions"}, "")) + + pattern_AuthorizerService_ListPermissions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "list_permissions"}, "")) +) + +var ( + forward_AuthorizerService_Signup_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Login_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Logout_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_MagicLinkLogin_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_VerifyEmail_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ResendVerifyEmail_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_VerifyOtp_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ResendOtp_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ForgotPassword_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ResetPassword_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Profile_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_UpdateProfile_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_DeactivateAccount_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Revoke_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Session_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ValidateJwtToken_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ValidateSession_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_Meta_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_CheckPermissions_0 = runtime.ForwardResponseMessage + + forward_AuthorizerService_ListPermissions_0 = runtime.ForwardResponseMessage +) diff --git a/gen/go/authorizer/v1/authorizer_grpc.pb.go b/gen/go/authorizer/v1/authorizer_grpc.pb.go new file mode 100644 index 00000000..424664cb --- /dev/null +++ b/gen/go/authorizer/v1/authorizer_grpc.pb.go @@ -0,0 +1,918 @@ +// AuthorizerService is the single gRPC service that exposes Authorizer's +// public API. Method names match the GraphQL operation names 1:1 +// (snake_case in GraphQL → PascalCase in proto): Signup, Login, +// MagicLinkLogin, VerifyEmail, ResendVerifyEmail, ForgotPassword, +// ResetPassword, VerifyOtp, ResendOtp, UpdateProfile, DeactivateAccount, +// Revoke, Meta, Session, Profile, ValidateJwtToken, ValidateSession, +// CheckPermissions, ListPermissions, Logout. +// +// Why one service: clients consume a single typed client per language, +// discovery is trivial, and the surface mirrors the GraphQL one users +// already know. The trade-off is that we lose resource-oriented evolution +// (no `List/Get/Create` symmetry per resource) — acceptable for an auth +// surface where most operations are stateless verbs anyway. +// +// REST mapping follows a simple rule: +// - GET /v1/{method} when the request body is empty (Meta, Profile, +// Logout) +// - POST /v1/{method} otherwise + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc (unknown) +// source: authorizer/v1/authorizer.proto + +package authorizerv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AuthorizerService_Signup_FullMethodName = "/authorizer.v1.AuthorizerService/Signup" + AuthorizerService_Login_FullMethodName = "/authorizer.v1.AuthorizerService/Login" + AuthorizerService_Logout_FullMethodName = "/authorizer.v1.AuthorizerService/Logout" + AuthorizerService_MagicLinkLogin_FullMethodName = "/authorizer.v1.AuthorizerService/MagicLinkLogin" + AuthorizerService_VerifyEmail_FullMethodName = "/authorizer.v1.AuthorizerService/VerifyEmail" + AuthorizerService_ResendVerifyEmail_FullMethodName = "/authorizer.v1.AuthorizerService/ResendVerifyEmail" + AuthorizerService_VerifyOtp_FullMethodName = "/authorizer.v1.AuthorizerService/VerifyOtp" + AuthorizerService_ResendOtp_FullMethodName = "/authorizer.v1.AuthorizerService/ResendOtp" + AuthorizerService_ForgotPassword_FullMethodName = "/authorizer.v1.AuthorizerService/ForgotPassword" + AuthorizerService_ResetPassword_FullMethodName = "/authorizer.v1.AuthorizerService/ResetPassword" + AuthorizerService_Profile_FullMethodName = "/authorizer.v1.AuthorizerService/Profile" + AuthorizerService_UpdateProfile_FullMethodName = "/authorizer.v1.AuthorizerService/UpdateProfile" + AuthorizerService_DeactivateAccount_FullMethodName = "/authorizer.v1.AuthorizerService/DeactivateAccount" + AuthorizerService_Revoke_FullMethodName = "/authorizer.v1.AuthorizerService/Revoke" + AuthorizerService_Session_FullMethodName = "/authorizer.v1.AuthorizerService/Session" + AuthorizerService_ValidateJwtToken_FullMethodName = "/authorizer.v1.AuthorizerService/ValidateJwtToken" + AuthorizerService_ValidateSession_FullMethodName = "/authorizer.v1.AuthorizerService/ValidateSession" + AuthorizerService_Meta_FullMethodName = "/authorizer.v1.AuthorizerService/Meta" + AuthorizerService_CheckPermissions_FullMethodName = "/authorizer.v1.AuthorizerService/CheckPermissions" + AuthorizerService_ListPermissions_FullMethodName = "/authorizer.v1.AuthorizerService/ListPermissions" +) + +// AuthorizerServiceClient is the client API for AuthorizerService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AuthorizerServiceClient interface { + // Signup registers a new user. Public; requires sign-up enabled in config. + // Returns AuthResponse with tokens + (browser callers) Set-Cookie headers. + Signup(ctx context.Context, in *SignupRequest, opts ...grpc.CallOption) (*SignupResponse, error) + // Login authenticates with email/phone + password. + Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) + // Logout ends the caller's current session. POST, not GET: logout mutates + // server state (clears the session) and is audit-logged, so it must not be + // a "safe" method per RFC 9110 §9.2.1 — GET would also expose it to CSRF. + Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) + // MagicLinkLogin dispatches a passwordless email link. The clicked link + // hits the existing /verify_email browser handler. + MagicLinkLogin(ctx context.Context, in *MagicLinkLoginRequest, opts ...grpc.CallOption) (*MagicLinkLoginResponse, error) + VerifyEmail(ctx context.Context, in *VerifyEmailRequest, opts ...grpc.CallOption) (*VerifyEmailResponse, error) + ResendVerifyEmail(ctx context.Context, in *ResendVerifyEmailRequest, opts ...grpc.CallOption) (*ResendVerifyEmailResponse, error) + VerifyOtp(ctx context.Context, in *VerifyOtpRequest, opts ...grpc.CallOption) (*VerifyOtpResponse, error) + ResendOtp(ctx context.Context, in *ResendOtpRequest, opts ...grpc.CallOption) (*ResendOtpResponse, error) + ForgotPassword(ctx context.Context, in *ForgotPasswordRequest, opts ...grpc.CallOption) (*ForgotPasswordResponse, error) + ResetPassword(ctx context.Context, in *ResetPasswordRequest, opts ...grpc.CallOption) (*ResetPasswordResponse, error) + // Profile returns the authenticated user. + Profile(ctx context.Context, in *ProfileRequest, opts ...grpc.CallOption) (*ProfileResponse, error) + UpdateProfile(ctx context.Context, in *UpdateProfileRequest, opts ...grpc.CallOption) (*UpdateProfileResponse, error) + // DeactivateAccount soft-deletes the caller's account and revokes all + // refresh tokens as a side effect. OAuth has no concept of account + // deactivation; this is the typed equivalent of SCIM PATCH active=false. + DeactivateAccount(ctx context.Context, in *DeactivateAccountRequest, opts ...grpc.CallOption) (*DeactivateAccountResponse, error) + // Revoke invalidates a refresh token. Typed mirror of RFC 7009. + Revoke(ctx context.Context, in *RevokeRequest, opts ...grpc.CallOption) (*RevokeResponse, error) + // Session returns the AuthResponse bound to the caller's cookie/bearer. + // NOT exposed as an MCP tool — SessionResponse carries access_token, + // refresh_token, id_token, authenticator_secret, and recovery codes, + // none of which should land in an LLM transcript. (Security audit C1.) + Session(ctx context.Context, in *SessionRequest, opts ...grpc.CallOption) (*SessionResponse, error) + ValidateJwtToken(ctx context.Context, in *ValidateJwtTokenRequest, opts ...grpc.CallOption) (*ValidateJwtTokenResponse, error) + ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error) + // Meta returns server feature flags. No auth required. + Meta(ctx context.Context, in *MetaRequest, opts ...grpc.CallOption) (*MetaResponse, error) + // CheckPermissions evaluates one or more fine-grained permission checks + // ("does the subject have on ?") in a single call and + // returns one result per check, in order. The subject defaults to the + // authenticated caller; an explicit `user` is honored only for super-admins + // or when it equals the caller's own subject. Fail-closed. + CheckPermissions(ctx context.Context, in *CheckPermissionsRequest, opts ...grpc.CallOption) (*CheckPermissionsResponse, error) + // ListPermissions enumerates what the subject can access. With both + // `relation` and `object_type` set it answers "which s can I + // ?"; either or both filters may be omitted to enumerate every + // matching (type, relation) pair of the active model. Results are capped; + // `truncated` reports when more permissions exist. Subject resolution + // follows the same rules as CheckPermissions. + ListPermissions(ctx context.Context, in *ListPermissionsRequest, opts ...grpc.CallOption) (*ListPermissionsResponse, error) +} + +type authorizerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAuthorizerServiceClient(cc grpc.ClientConnInterface) AuthorizerServiceClient { + return &authorizerServiceClient{cc} +} + +func (c *authorizerServiceClient) Signup(ctx context.Context, in *SignupRequest, opts ...grpc.CallOption) (*SignupResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SignupResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Signup_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LoginResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Login_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LogoutResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Logout_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) MagicLinkLogin(ctx context.Context, in *MagicLinkLoginRequest, opts ...grpc.CallOption) (*MagicLinkLoginResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MagicLinkLoginResponse) + err := c.cc.Invoke(ctx, AuthorizerService_MagicLinkLogin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) VerifyEmail(ctx context.Context, in *VerifyEmailRequest, opts ...grpc.CallOption) (*VerifyEmailResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(VerifyEmailResponse) + err := c.cc.Invoke(ctx, AuthorizerService_VerifyEmail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ResendVerifyEmail(ctx context.Context, in *ResendVerifyEmailRequest, opts ...grpc.CallOption) (*ResendVerifyEmailResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ResendVerifyEmailResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ResendVerifyEmail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) VerifyOtp(ctx context.Context, in *VerifyOtpRequest, opts ...grpc.CallOption) (*VerifyOtpResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(VerifyOtpResponse) + err := c.cc.Invoke(ctx, AuthorizerService_VerifyOtp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ResendOtp(ctx context.Context, in *ResendOtpRequest, opts ...grpc.CallOption) (*ResendOtpResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ResendOtpResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ResendOtp_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ForgotPassword(ctx context.Context, in *ForgotPasswordRequest, opts ...grpc.CallOption) (*ForgotPasswordResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ForgotPasswordResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ForgotPassword_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ResetPassword(ctx context.Context, in *ResetPasswordRequest, opts ...grpc.CallOption) (*ResetPasswordResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ResetPasswordResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ResetPassword_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Profile(ctx context.Context, in *ProfileRequest, opts ...grpc.CallOption) (*ProfileResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ProfileResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Profile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) UpdateProfile(ctx context.Context, in *UpdateProfileRequest, opts ...grpc.CallOption) (*UpdateProfileResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateProfileResponse) + err := c.cc.Invoke(ctx, AuthorizerService_UpdateProfile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) DeactivateAccount(ctx context.Context, in *DeactivateAccountRequest, opts ...grpc.CallOption) (*DeactivateAccountResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeactivateAccountResponse) + err := c.cc.Invoke(ctx, AuthorizerService_DeactivateAccount_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Revoke(ctx context.Context, in *RevokeRequest, opts ...grpc.CallOption) (*RevokeResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Revoke_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Session(ctx context.Context, in *SessionRequest, opts ...grpc.CallOption) (*SessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SessionResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Session_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ValidateJwtToken(ctx context.Context, in *ValidateJwtTokenRequest, opts ...grpc.CallOption) (*ValidateJwtTokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ValidateJwtTokenResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ValidateJwtToken_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ValidateSessionResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ValidateSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) Meta(ctx context.Context, in *MetaRequest, opts ...grpc.CallOption) (*MetaResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MetaResponse) + err := c.cc.Invoke(ctx, AuthorizerService_Meta_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) CheckPermissions(ctx context.Context, in *CheckPermissionsRequest, opts ...grpc.CallOption) (*CheckPermissionsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CheckPermissionsResponse) + err := c.cc.Invoke(ctx, AuthorizerService_CheckPermissions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authorizerServiceClient) ListPermissions(ctx context.Context, in *ListPermissionsRequest, opts ...grpc.CallOption) (*ListPermissionsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPermissionsResponse) + err := c.cc.Invoke(ctx, AuthorizerService_ListPermissions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AuthorizerServiceServer is the server API for AuthorizerService service. +// All implementations should embed UnimplementedAuthorizerServiceServer +// for forward compatibility. +type AuthorizerServiceServer interface { + // Signup registers a new user. Public; requires sign-up enabled in config. + // Returns AuthResponse with tokens + (browser callers) Set-Cookie headers. + Signup(context.Context, *SignupRequest) (*SignupResponse, error) + // Login authenticates with email/phone + password. + Login(context.Context, *LoginRequest) (*LoginResponse, error) + // Logout ends the caller's current session. POST, not GET: logout mutates + // server state (clears the session) and is audit-logged, so it must not be + // a "safe" method per RFC 9110 §9.2.1 — GET would also expose it to CSRF. + Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) + // MagicLinkLogin dispatches a passwordless email link. The clicked link + // hits the existing /verify_email browser handler. + MagicLinkLogin(context.Context, *MagicLinkLoginRequest) (*MagicLinkLoginResponse, error) + VerifyEmail(context.Context, *VerifyEmailRequest) (*VerifyEmailResponse, error) + ResendVerifyEmail(context.Context, *ResendVerifyEmailRequest) (*ResendVerifyEmailResponse, error) + VerifyOtp(context.Context, *VerifyOtpRequest) (*VerifyOtpResponse, error) + ResendOtp(context.Context, *ResendOtpRequest) (*ResendOtpResponse, error) + ForgotPassword(context.Context, *ForgotPasswordRequest) (*ForgotPasswordResponse, error) + ResetPassword(context.Context, *ResetPasswordRequest) (*ResetPasswordResponse, error) + // Profile returns the authenticated user. + Profile(context.Context, *ProfileRequest) (*ProfileResponse, error) + UpdateProfile(context.Context, *UpdateProfileRequest) (*UpdateProfileResponse, error) + // DeactivateAccount soft-deletes the caller's account and revokes all + // refresh tokens as a side effect. OAuth has no concept of account + // deactivation; this is the typed equivalent of SCIM PATCH active=false. + DeactivateAccount(context.Context, *DeactivateAccountRequest) (*DeactivateAccountResponse, error) + // Revoke invalidates a refresh token. Typed mirror of RFC 7009. + Revoke(context.Context, *RevokeRequest) (*RevokeResponse, error) + // Session returns the AuthResponse bound to the caller's cookie/bearer. + // NOT exposed as an MCP tool — SessionResponse carries access_token, + // refresh_token, id_token, authenticator_secret, and recovery codes, + // none of which should land in an LLM transcript. (Security audit C1.) + Session(context.Context, *SessionRequest) (*SessionResponse, error) + ValidateJwtToken(context.Context, *ValidateJwtTokenRequest) (*ValidateJwtTokenResponse, error) + ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) + // Meta returns server feature flags. No auth required. + Meta(context.Context, *MetaRequest) (*MetaResponse, error) + // CheckPermissions evaluates one or more fine-grained permission checks + // ("does the subject have on ?") in a single call and + // returns one result per check, in order. The subject defaults to the + // authenticated caller; an explicit `user` is honored only for super-admins + // or when it equals the caller's own subject. Fail-closed. + CheckPermissions(context.Context, *CheckPermissionsRequest) (*CheckPermissionsResponse, error) + // ListPermissions enumerates what the subject can access. With both + // `relation` and `object_type` set it answers "which s can I + // ?"; either or both filters may be omitted to enumerate every + // matching (type, relation) pair of the active model. Results are capped; + // `truncated` reports when more permissions exist. Subject resolution + // follows the same rules as CheckPermissions. + ListPermissions(context.Context, *ListPermissionsRequest) (*ListPermissionsResponse, error) +} + +// UnimplementedAuthorizerServiceServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAuthorizerServiceServer struct{} + +func (UnimplementedAuthorizerServiceServer) Signup(context.Context, *SignupRequest) (*SignupResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Signup not implemented") +} +func (UnimplementedAuthorizerServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} +func (UnimplementedAuthorizerServiceServer) Logout(context.Context, *LogoutRequest) (*LogoutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") +} +func (UnimplementedAuthorizerServiceServer) MagicLinkLogin(context.Context, *MagicLinkLoginRequest) (*MagicLinkLoginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MagicLinkLogin not implemented") +} +func (UnimplementedAuthorizerServiceServer) VerifyEmail(context.Context, *VerifyEmailRequest) (*VerifyEmailResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VerifyEmail not implemented") +} +func (UnimplementedAuthorizerServiceServer) ResendVerifyEmail(context.Context, *ResendVerifyEmailRequest) (*ResendVerifyEmailResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResendVerifyEmail not implemented") +} +func (UnimplementedAuthorizerServiceServer) VerifyOtp(context.Context, *VerifyOtpRequest) (*VerifyOtpResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VerifyOtp not implemented") +} +func (UnimplementedAuthorizerServiceServer) ResendOtp(context.Context, *ResendOtpRequest) (*ResendOtpResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResendOtp not implemented") +} +func (UnimplementedAuthorizerServiceServer) ForgotPassword(context.Context, *ForgotPasswordRequest) (*ForgotPasswordResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForgotPassword not implemented") +} +func (UnimplementedAuthorizerServiceServer) ResetPassword(context.Context, *ResetPasswordRequest) (*ResetPasswordResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ResetPassword not implemented") +} +func (UnimplementedAuthorizerServiceServer) Profile(context.Context, *ProfileRequest) (*ProfileResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Profile not implemented") +} +func (UnimplementedAuthorizerServiceServer) UpdateProfile(context.Context, *UpdateProfileRequest) (*UpdateProfileResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateProfile not implemented") +} +func (UnimplementedAuthorizerServiceServer) DeactivateAccount(context.Context, *DeactivateAccountRequest) (*DeactivateAccountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeactivateAccount not implemented") +} +func (UnimplementedAuthorizerServiceServer) Revoke(context.Context, *RevokeRequest) (*RevokeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Revoke not implemented") +} +func (UnimplementedAuthorizerServiceServer) Session(context.Context, *SessionRequest) (*SessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Session not implemented") +} +func (UnimplementedAuthorizerServiceServer) ValidateJwtToken(context.Context, *ValidateJwtTokenRequest) (*ValidateJwtTokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ValidateJwtToken not implemented") +} +func (UnimplementedAuthorizerServiceServer) ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ValidateSession not implemented") +} +func (UnimplementedAuthorizerServiceServer) Meta(context.Context, *MetaRequest) (*MetaResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Meta not implemented") +} +func (UnimplementedAuthorizerServiceServer) CheckPermissions(context.Context, *CheckPermissionsRequest) (*CheckPermissionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckPermissions not implemented") +} +func (UnimplementedAuthorizerServiceServer) ListPermissions(context.Context, *ListPermissionsRequest) (*ListPermissionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListPermissions not implemented") +} +func (UnimplementedAuthorizerServiceServer) testEmbeddedByValue() {} + +// UnsafeAuthorizerServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AuthorizerServiceServer will +// result in compilation errors. +type UnsafeAuthorizerServiceServer interface { + mustEmbedUnimplementedAuthorizerServiceServer() +} + +func RegisterAuthorizerServiceServer(s grpc.ServiceRegistrar, srv AuthorizerServiceServer) { + // If the following call pancis, it indicates UnimplementedAuthorizerServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AuthorizerService_ServiceDesc, srv) +} + +func _AuthorizerService_Signup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SignupRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Signup(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Signup_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Signup(ctx, req.(*SignupRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Login_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Login(ctx, req.(*LoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LogoutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Logout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Logout_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Logout(ctx, req.(*LogoutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_MagicLinkLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MagicLinkLoginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).MagicLinkLogin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_MagicLinkLogin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).MagicLinkLogin(ctx, req.(*MagicLinkLoginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_VerifyEmail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VerifyEmailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).VerifyEmail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_VerifyEmail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).VerifyEmail(ctx, req.(*VerifyEmailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ResendVerifyEmail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResendVerifyEmailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ResendVerifyEmail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ResendVerifyEmail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ResendVerifyEmail(ctx, req.(*ResendVerifyEmailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_VerifyOtp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VerifyOtpRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).VerifyOtp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_VerifyOtp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).VerifyOtp(ctx, req.(*VerifyOtpRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ResendOtp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResendOtpRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ResendOtp(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ResendOtp_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ResendOtp(ctx, req.(*ResendOtpRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ForgotPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForgotPasswordRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ForgotPassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ForgotPassword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ForgotPassword(ctx, req.(*ForgotPasswordRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ResetPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResetPasswordRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ResetPassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ResetPassword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ResetPassword(ctx, req.(*ResetPasswordRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Profile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Profile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Profile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Profile(ctx, req.(*ProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_UpdateProfile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateProfileRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).UpdateProfile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_UpdateProfile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).UpdateProfile(ctx, req.(*UpdateProfileRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_DeactivateAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeactivateAccountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).DeactivateAccount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_DeactivateAccount_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).DeactivateAccount(ctx, req.(*DeactivateAccountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Revoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Revoke(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Revoke_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Revoke(ctx, req.(*RevokeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Session_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Session(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Session_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Session(ctx, req.(*SessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ValidateJwtToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ValidateJwtTokenRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ValidateJwtToken(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ValidateJwtToken_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ValidateJwtToken(ctx, req.(*ValidateJwtTokenRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ValidateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ValidateSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ValidateSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ValidateSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ValidateSession(ctx, req.(*ValidateSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_Meta_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetaRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).Meta(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_Meta_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).Meta(ctx, req.(*MetaRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_CheckPermissions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CheckPermissionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).CheckPermissions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_CheckPermissions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).CheckPermissions(ctx, req.(*CheckPermissionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AuthorizerService_ListPermissions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPermissionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthorizerServiceServer).ListPermissions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AuthorizerService_ListPermissions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthorizerServiceServer).ListPermissions(ctx, req.(*ListPermissionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AuthorizerService_ServiceDesc is the grpc.ServiceDesc for AuthorizerService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AuthorizerService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "authorizer.v1.AuthorizerService", + HandlerType: (*AuthorizerServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Signup", + Handler: _AuthorizerService_Signup_Handler, + }, + { + MethodName: "Login", + Handler: _AuthorizerService_Login_Handler, + }, + { + MethodName: "Logout", + Handler: _AuthorizerService_Logout_Handler, + }, + { + MethodName: "MagicLinkLogin", + Handler: _AuthorizerService_MagicLinkLogin_Handler, + }, + { + MethodName: "VerifyEmail", + Handler: _AuthorizerService_VerifyEmail_Handler, + }, + { + MethodName: "ResendVerifyEmail", + Handler: _AuthorizerService_ResendVerifyEmail_Handler, + }, + { + MethodName: "VerifyOtp", + Handler: _AuthorizerService_VerifyOtp_Handler, + }, + { + MethodName: "ResendOtp", + Handler: _AuthorizerService_ResendOtp_Handler, + }, + { + MethodName: "ForgotPassword", + Handler: _AuthorizerService_ForgotPassword_Handler, + }, + { + MethodName: "ResetPassword", + Handler: _AuthorizerService_ResetPassword_Handler, + }, + { + MethodName: "Profile", + Handler: _AuthorizerService_Profile_Handler, + }, + { + MethodName: "UpdateProfile", + Handler: _AuthorizerService_UpdateProfile_Handler, + }, + { + MethodName: "DeactivateAccount", + Handler: _AuthorizerService_DeactivateAccount_Handler, + }, + { + MethodName: "Revoke", + Handler: _AuthorizerService_Revoke_Handler, + }, + { + MethodName: "Session", + Handler: _AuthorizerService_Session_Handler, + }, + { + MethodName: "ValidateJwtToken", + Handler: _AuthorizerService_ValidateJwtToken_Handler, + }, + { + MethodName: "ValidateSession", + Handler: _AuthorizerService_ValidateSession_Handler, + }, + { + MethodName: "Meta", + Handler: _AuthorizerService_Meta_Handler, + }, + { + MethodName: "CheckPermissions", + Handler: _AuthorizerService_CheckPermissions_Handler, + }, + { + MethodName: "ListPermissions", + Handler: _AuthorizerService_ListPermissions_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "authorizer/v1/authorizer.proto", +} diff --git a/gen/go/authorizer/v1/types.pb.go b/gen/go/authorizer/v1/types.pb.go new file mode 100644 index 00000000..c62a1b23 --- /dev/null +++ b/gen/go/authorizer/v1/types.pb.go @@ -0,0 +1,1132 @@ +// Shared message types used by the Authorizer service. Field naming mirrors +// the GraphQL schema 1:1 so REST/gRPC clients see the same shape. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: authorizer/v1/types.proto + +package authorizerv1 + +import ( + v1 "github.com/authorizerdev/authorizer/gen/go/authorizer/common/v1" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// User mirrors the GraphQL User type. Returned by Profile and embedded in +// AuthResponse. +type User struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Either email or phone_number is always present. + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + EmailVerified bool `protobuf:"varint,3,opt,name=email_verified,json=emailVerified,proto3" json:"email_verified,omitempty"` + SignupMethods string `protobuf:"bytes,4,opt,name=signup_methods,json=signupMethods,proto3" json:"signup_methods,omitempty"` + GivenName string `protobuf:"bytes,5,opt,name=given_name,json=givenName,proto3" json:"given_name,omitempty"` + FamilyName string `protobuf:"bytes,6,opt,name=family_name,json=familyName,proto3" json:"family_name,omitempty"` + MiddleName string `protobuf:"bytes,7,opt,name=middle_name,json=middleName,proto3" json:"middle_name,omitempty"` + Nickname string `protobuf:"bytes,8,opt,name=nickname,proto3" json:"nickname,omitempty"` + // Defaults to email when unset. + PreferredUsername string `protobuf:"bytes,9,opt,name=preferred_username,json=preferredUsername,proto3" json:"preferred_username,omitempty"` + Gender string `protobuf:"bytes,10,opt,name=gender,proto3" json:"gender,omitempty"` + Birthdate string `protobuf:"bytes,11,opt,name=birthdate,proto3" json:"birthdate,omitempty"` + PhoneNumber string `protobuf:"bytes,12,opt,name=phone_number,json=phoneNumber,proto3" json:"phone_number,omitempty"` + PhoneNumberVerified bool `protobuf:"varint,13,opt,name=phone_number_verified,json=phoneNumberVerified,proto3" json:"phone_number_verified,omitempty"` + Picture string `protobuf:"bytes,14,opt,name=picture,proto3" json:"picture,omitempty"` + Roles []string `protobuf:"bytes,15,rep,name=roles,proto3" json:"roles,omitempty"` + CreatedAt int64 `protobuf:"varint,16,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt int64 `protobuf:"varint,17,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + RevokedTimestamp int64 `protobuf:"varint,18,opt,name=revoked_timestamp,json=revokedTimestamp,proto3" json:"revoked_timestamp,omitempty"` + IsMultiFactorAuthEnabled bool `protobuf:"varint,19,opt,name=is_multi_factor_auth_enabled,json=isMultiFactorAuthEnabled,proto3" json:"is_multi_factor_auth_enabled,omitempty"` + // Free-form key/value bag — same as GraphQL `app_data: Map`. + AppData *v1.AppData `protobuf:"bytes,20,opt,name=app_data,json=appData,proto3" json:"app_data,omitempty"` +} + +func (x *User) Reset() { + *x = User{} + mi := &file_authorizer_v1_types_proto_msgTypes[0] + 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_authorizer_v1_types_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use User.ProtoReflect.Descriptor instead. +func (*User) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{0} +} + +func (x *User) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *User) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *User) GetEmailVerified() bool { + if x != nil { + return x.EmailVerified + } + return false +} + +func (x *User) GetSignupMethods() string { + if x != nil { + return x.SignupMethods + } + return "" +} + +func (x *User) GetGivenName() string { + if x != nil { + return x.GivenName + } + return "" +} + +func (x *User) GetFamilyName() string { + if x != nil { + return x.FamilyName + } + return "" +} + +func (x *User) GetMiddleName() string { + if x != nil { + return x.MiddleName + } + return "" +} + +func (x *User) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *User) GetPreferredUsername() string { + if x != nil { + return x.PreferredUsername + } + return "" +} + +func (x *User) GetGender() string { + if x != nil { + return x.Gender + } + return "" +} + +func (x *User) GetBirthdate() string { + if x != nil { + return x.Birthdate + } + return "" +} + +func (x *User) GetPhoneNumber() string { + if x != nil { + return x.PhoneNumber + } + return "" +} + +func (x *User) GetPhoneNumberVerified() bool { + if x != nil { + return x.PhoneNumberVerified + } + return false +} + +func (x *User) GetPicture() string { + if x != nil { + return x.Picture + } + return "" +} + +func (x *User) GetRoles() []string { + if x != nil { + return x.Roles + } + return nil +} + +func (x *User) GetCreatedAt() int64 { + if x != nil { + return x.CreatedAt + } + return 0 +} + +func (x *User) GetUpdatedAt() int64 { + if x != nil { + return x.UpdatedAt + } + return 0 +} + +func (x *User) GetRevokedTimestamp() int64 { + if x != nil { + return x.RevokedTimestamp + } + return 0 +} + +func (x *User) GetIsMultiFactorAuthEnabled() bool { + if x != nil { + return x.IsMultiFactorAuthEnabled + } + return false +} + +func (x *User) GetAppData() *v1.AppData { + if x != nil { + return x.AppData + } + return nil +} + +// AuthResponse mirrors the GraphQL AuthResponse type. Returned (wrapped) by +// every method that produces a session: Signup, Login, MagicLinkLogin, +// VerifyEmail, VerifyOtp, Session. +type AuthResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + ShouldShowEmailOtpScreen bool `protobuf:"varint,2,opt,name=should_show_email_otp_screen,json=shouldShowEmailOtpScreen,proto3" json:"should_show_email_otp_screen,omitempty"` + ShouldShowMobileOtpScreen bool `protobuf:"varint,3,opt,name=should_show_mobile_otp_screen,json=shouldShowMobileOtpScreen,proto3" json:"should_show_mobile_otp_screen,omitempty"` + ShouldShowTotpScreen bool `protobuf:"varint,4,opt,name=should_show_totp_screen,json=shouldShowTotpScreen,proto3" json:"should_show_totp_screen,omitempty"` + AccessToken string `protobuf:"bytes,5,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + IdToken string `protobuf:"bytes,6,opt,name=id_token,json=idToken,proto3" json:"id_token,omitempty"` + RefreshToken string `protobuf:"bytes,7,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + ExpiresIn int64 `protobuf:"varint,8,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"` + User *User `protobuf:"bytes,9,opt,name=user,proto3" json:"user,omitempty"` + // TOTP enrolment artifacts (populated only when this AuthResponse + // initiates TOTP setup; one-time, never re-shown). + AuthenticatorScannerImage string `protobuf:"bytes,10,opt,name=authenticator_scanner_image,json=authenticatorScannerImage,proto3" json:"authenticator_scanner_image,omitempty"` + AuthenticatorSecret string `protobuf:"bytes,11,opt,name=authenticator_secret,json=authenticatorSecret,proto3" json:"authenticator_secret,omitempty"` + AuthenticatorRecoveryCodes []string `protobuf:"bytes,12,rep,name=authenticator_recovery_codes,json=authenticatorRecoveryCodes,proto3" json:"authenticator_recovery_codes,omitempty"` +} + +func (x *AuthResponse) Reset() { + *x = AuthResponse{} + mi := &file_authorizer_v1_types_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AuthResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthResponse) ProtoMessage() {} + +func (x *AuthResponse) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead. +func (*AuthResponse) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{1} +} + +func (x *AuthResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *AuthResponse) GetShouldShowEmailOtpScreen() bool { + if x != nil { + return x.ShouldShowEmailOtpScreen + } + return false +} + +func (x *AuthResponse) GetShouldShowMobileOtpScreen() bool { + if x != nil { + return x.ShouldShowMobileOtpScreen + } + return false +} + +func (x *AuthResponse) GetShouldShowTotpScreen() bool { + if x != nil { + return x.ShouldShowTotpScreen + } + return false +} + +func (x *AuthResponse) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *AuthResponse) GetIdToken() string { + if x != nil { + return x.IdToken + } + return "" +} + +func (x *AuthResponse) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +func (x *AuthResponse) GetExpiresIn() int64 { + if x != nil { + return x.ExpiresIn + } + return 0 +} + +func (x *AuthResponse) GetUser() *User { + if x != nil { + return x.User + } + return nil +} + +func (x *AuthResponse) GetAuthenticatorScannerImage() string { + if x != nil { + return x.AuthenticatorScannerImage + } + return "" +} + +func (x *AuthResponse) GetAuthenticatorSecret() string { + if x != nil { + return x.AuthenticatorSecret + } + return "" +} + +func (x *AuthResponse) GetAuthenticatorRecoveryCodes() []string { + if x != nil { + return x.AuthenticatorRecoveryCodes + } + return nil +} + +// Permission is one (object, relation) pair the subject holds: "subject has +// `relation` on `object`". Mirrors the GraphQL Permission type. +type Permission struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Object string `protobuf:"bytes,1,opt,name=object,proto3" json:"object,omitempty"` + Relation string `protobuf:"bytes,2,opt,name=relation,proto3" json:"relation,omitempty"` +} + +func (x *Permission) Reset() { + *x = Permission{} + mi := &file_authorizer_v1_types_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Permission) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Permission) ProtoMessage() {} + +func (x *Permission) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Permission.ProtoReflect.Descriptor instead. +func (*Permission) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{2} +} + +func (x *Permission) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *Permission) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +// FgaRelationInput is a (relation, object) requirement evaluated against the +// authenticated caller during Session / ValidateJwtToken / ValidateSession. +// AND semantics, fail-closed. Mirrors the GraphQL FgaRelationInput type. +type FgaRelationInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Relation string `protobuf:"bytes,1,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,2,opt,name=object,proto3" json:"object,omitempty"` +} + +func (x *FgaRelationInput) Reset() { + *x = FgaRelationInput{} + mi := &file_authorizer_v1_types_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FgaRelationInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FgaRelationInput) ProtoMessage() {} + +func (x *FgaRelationInput) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FgaRelationInput.ProtoReflect.Descriptor instead. +func (*FgaRelationInput) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{3} +} + +func (x *FgaRelationInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *FgaRelationInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +// FgaTupleInput is one relationship tuple supplied as request-scoped context +// for a permission check; contextual tuples are never persisted. Mirrors the +// GraphQL FgaTupleInput type. +type FgaTupleInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + Relation string `protobuf:"bytes,2,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"` +} + +func (x *FgaTupleInput) Reset() { + *x = FgaTupleInput{} + mi := &file_authorizer_v1_types_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FgaTupleInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FgaTupleInput) ProtoMessage() {} + +func (x *FgaTupleInput) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FgaTupleInput.ProtoReflect.Descriptor instead. +func (*FgaTupleInput) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{4} +} + +func (x *FgaTupleInput) GetUser() string { + if x != nil { + return x.User + } + return "" +} + +func (x *FgaTupleInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *FgaTupleInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +// PermissionCheckInput is one permission to evaluate: "does the subject have +// on ?". Mirrors the GraphQL PermissionCheckInput type. +type PermissionCheckInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Relation string `protobuf:"bytes,1,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,2,opt,name=object,proto3" json:"object,omitempty"` + ContextualTuples []*FgaTupleInput `protobuf:"bytes,3,rep,name=contextual_tuples,json=contextualTuples,proto3" json:"contextual_tuples,omitempty"` +} + +func (x *PermissionCheckInput) Reset() { + *x = PermissionCheckInput{} + mi := &file_authorizer_v1_types_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PermissionCheckInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PermissionCheckInput) ProtoMessage() {} + +func (x *PermissionCheckInput) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PermissionCheckInput.ProtoReflect.Descriptor instead. +func (*PermissionCheckInput) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{5} +} + +func (x *PermissionCheckInput) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *PermissionCheckInput) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *PermissionCheckInput) GetContextualTuples() []*FgaTupleInput { + if x != nil { + return x.ContextualTuples + } + return nil +} + +// PermissionCheckResult is the outcome of one permission check, echoing the +// checked pair so batch results are self-describing (and positionally +// aligned). Mirrors the GraphQL PermissionCheckResult type. +type PermissionCheckResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Relation string `protobuf:"bytes,1,opt,name=relation,proto3" json:"relation,omitempty"` + Object string `protobuf:"bytes,2,opt,name=object,proto3" json:"object,omitempty"` + Allowed bool `protobuf:"varint,3,opt,name=allowed,proto3" json:"allowed,omitempty"` +} + +func (x *PermissionCheckResult) Reset() { + *x = PermissionCheckResult{} + mi := &file_authorizer_v1_types_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PermissionCheckResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PermissionCheckResult) ProtoMessage() {} + +func (x *PermissionCheckResult) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PermissionCheckResult.ProtoReflect.Descriptor instead. +func (*PermissionCheckResult) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{6} +} + +func (x *PermissionCheckResult) GetRelation() string { + if x != nil { + return x.Relation + } + return "" +} + +func (x *PermissionCheckResult) GetObject() string { + if x != nil { + return x.Object + } + return "" +} + +func (x *PermissionCheckResult) GetAllowed() bool { + if x != nil { + return x.Allowed + } + return false +} + +// Meta mirrors the GraphQL Meta type — server feature flags + provider +// availability, returned by the Meta query. +type Meta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + IsGoogleLoginEnabled bool `protobuf:"varint,3,opt,name=is_google_login_enabled,json=isGoogleLoginEnabled,proto3" json:"is_google_login_enabled,omitempty"` + IsFacebookLoginEnabled bool `protobuf:"varint,4,opt,name=is_facebook_login_enabled,json=isFacebookLoginEnabled,proto3" json:"is_facebook_login_enabled,omitempty"` + IsGithubLoginEnabled bool `protobuf:"varint,5,opt,name=is_github_login_enabled,json=isGithubLoginEnabled,proto3" json:"is_github_login_enabled,omitempty"` + IsLinkedinLoginEnabled bool `protobuf:"varint,6,opt,name=is_linkedin_login_enabled,json=isLinkedinLoginEnabled,proto3" json:"is_linkedin_login_enabled,omitempty"` + IsAppleLoginEnabled bool `protobuf:"varint,7,opt,name=is_apple_login_enabled,json=isAppleLoginEnabled,proto3" json:"is_apple_login_enabled,omitempty"` + IsDiscordLoginEnabled bool `protobuf:"varint,8,opt,name=is_discord_login_enabled,json=isDiscordLoginEnabled,proto3" json:"is_discord_login_enabled,omitempty"` + IsTwitterLoginEnabled bool `protobuf:"varint,9,opt,name=is_twitter_login_enabled,json=isTwitterLoginEnabled,proto3" json:"is_twitter_login_enabled,omitempty"` + IsMicrosoftLoginEnabled bool `protobuf:"varint,10,opt,name=is_microsoft_login_enabled,json=isMicrosoftLoginEnabled,proto3" json:"is_microsoft_login_enabled,omitempty"` + IsTwitchLoginEnabled bool `protobuf:"varint,11,opt,name=is_twitch_login_enabled,json=isTwitchLoginEnabled,proto3" json:"is_twitch_login_enabled,omitempty"` + IsRobloxLoginEnabled bool `protobuf:"varint,12,opt,name=is_roblox_login_enabled,json=isRobloxLoginEnabled,proto3" json:"is_roblox_login_enabled,omitempty"` + IsEmailVerificationEnabled bool `protobuf:"varint,13,opt,name=is_email_verification_enabled,json=isEmailVerificationEnabled,proto3" json:"is_email_verification_enabled,omitempty"` + IsBasicAuthenticationEnabled bool `protobuf:"varint,14,opt,name=is_basic_authentication_enabled,json=isBasicAuthenticationEnabled,proto3" json:"is_basic_authentication_enabled,omitempty"` + IsMagicLinkLoginEnabled bool `protobuf:"varint,15,opt,name=is_magic_link_login_enabled,json=isMagicLinkLoginEnabled,proto3" json:"is_magic_link_login_enabled,omitempty"` + IsSignUpEnabled bool `protobuf:"varint,16,opt,name=is_sign_up_enabled,json=isSignUpEnabled,proto3" json:"is_sign_up_enabled,omitempty"` + IsStrongPasswordEnabled bool `protobuf:"varint,17,opt,name=is_strong_password_enabled,json=isStrongPasswordEnabled,proto3" json:"is_strong_password_enabled,omitempty"` + IsMultiFactorAuthEnabled bool `protobuf:"varint,18,opt,name=is_multi_factor_auth_enabled,json=isMultiFactorAuthEnabled,proto3" json:"is_multi_factor_auth_enabled,omitempty"` + IsMobileBasicAuthenticationEnabled bool `protobuf:"varint,19,opt,name=is_mobile_basic_authentication_enabled,json=isMobileBasicAuthenticationEnabled,proto3" json:"is_mobile_basic_authentication_enabled,omitempty"` + IsPhoneVerificationEnabled bool `protobuf:"varint,20,opt,name=is_phone_verification_enabled,json=isPhoneVerificationEnabled,proto3" json:"is_phone_verification_enabled,omitempty"` +} + +func (x *Meta) Reset() { + *x = Meta{} + mi := &file_authorizer_v1_types_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Meta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Meta) ProtoMessage() {} + +func (x *Meta) ProtoReflect() protoreflect.Message { + mi := &file_authorizer_v1_types_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Meta.ProtoReflect.Descriptor instead. +func (*Meta) Descriptor() ([]byte, []int) { + return file_authorizer_v1_types_proto_rawDescGZIP(), []int{7} +} + +func (x *Meta) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *Meta) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *Meta) GetIsGoogleLoginEnabled() bool { + if x != nil { + return x.IsGoogleLoginEnabled + } + return false +} + +func (x *Meta) GetIsFacebookLoginEnabled() bool { + if x != nil { + return x.IsFacebookLoginEnabled + } + return false +} + +func (x *Meta) GetIsGithubLoginEnabled() bool { + if x != nil { + return x.IsGithubLoginEnabled + } + return false +} + +func (x *Meta) GetIsLinkedinLoginEnabled() bool { + if x != nil { + return x.IsLinkedinLoginEnabled + } + return false +} + +func (x *Meta) GetIsAppleLoginEnabled() bool { + if x != nil { + return x.IsAppleLoginEnabled + } + return false +} + +func (x *Meta) GetIsDiscordLoginEnabled() bool { + if x != nil { + return x.IsDiscordLoginEnabled + } + return false +} + +func (x *Meta) GetIsTwitterLoginEnabled() bool { + if x != nil { + return x.IsTwitterLoginEnabled + } + return false +} + +func (x *Meta) GetIsMicrosoftLoginEnabled() bool { + if x != nil { + return x.IsMicrosoftLoginEnabled + } + return false +} + +func (x *Meta) GetIsTwitchLoginEnabled() bool { + if x != nil { + return x.IsTwitchLoginEnabled + } + return false +} + +func (x *Meta) GetIsRobloxLoginEnabled() bool { + if x != nil { + return x.IsRobloxLoginEnabled + } + return false +} + +func (x *Meta) GetIsEmailVerificationEnabled() bool { + if x != nil { + return x.IsEmailVerificationEnabled + } + return false +} + +func (x *Meta) GetIsBasicAuthenticationEnabled() bool { + if x != nil { + return x.IsBasicAuthenticationEnabled + } + return false +} + +func (x *Meta) GetIsMagicLinkLoginEnabled() bool { + if x != nil { + return x.IsMagicLinkLoginEnabled + } + return false +} + +func (x *Meta) GetIsSignUpEnabled() bool { + if x != nil { + return x.IsSignUpEnabled + } + return false +} + +func (x *Meta) GetIsStrongPasswordEnabled() bool { + if x != nil { + return x.IsStrongPasswordEnabled + } + return false +} + +func (x *Meta) GetIsMultiFactorAuthEnabled() bool { + if x != nil { + return x.IsMultiFactorAuthEnabled + } + return false +} + +func (x *Meta) GetIsMobileBasicAuthenticationEnabled() bool { + if x != nil { + return x.IsMobileBasicAuthenticationEnabled + } + return false +} + +func (x *Meta) GetIsPhoneVerificationEnabled() bool { + if x != nil { + return x.IsPhoneVerificationEnabled + } + return false +} + +var File_authorizer_v1_types_proto protoreflect.FileDescriptor + +var file_authorizer_v1_types_proto_rawDesc = []byte{ + 0x0a, 0x19, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x20, 0x61, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc8, 0x05, 0x0a, + 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x25, 0x0a, 0x0e, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0d, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, + 0x75, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x76, + 0x65, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, + 0x69, 0x76, 0x65, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x61, 0x6d, 0x69, + 0x6c, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x69, 0x64, + 0x64, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, + 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, + 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x72, 0x65, 0x64, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x11, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x55, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x0a, + 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x62, 0x69, 0x72, 0x74, 0x68, 0x64, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x32, + 0x0a, 0x15, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x70, + 0x68, 0x6f, 0x6e, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x69, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x72, 0x6f, 0x6c, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x72, 0x6f, 0x6c, + 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x11, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x12, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x72, 0x65, 0x76, + 0x6f, 0x6b, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3e, 0x0a, + 0x1c, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, + 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x18, 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x46, 0x61, 0x63, 0x74, + 0x6f, 0x72, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x38, 0x0a, + 0x08, 0x61, 0x70, 0x70, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, + 0x6d, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, + 0x61, 0x70, 0x70, 0x44, 0x61, 0x74, 0x61, 0x22, 0xc1, 0x04, 0x0a, 0x0c, 0x41, 0x75, 0x74, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x12, 0x3e, 0x0a, 0x1c, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x73, 0x68, 0x6f, + 0x77, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x5f, 0x6f, 0x74, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x65, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, + 0x53, 0x68, 0x6f, 0x77, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x4f, 0x74, 0x70, 0x53, 0x63, 0x72, 0x65, + 0x65, 0x6e, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x73, 0x68, 0x6f, + 0x77, 0x5f, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x5f, 0x6f, 0x74, 0x70, 0x5f, 0x73, 0x63, 0x72, + 0x65, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x73, 0x68, 0x6f, 0x75, 0x6c, + 0x64, 0x53, 0x68, 0x6f, 0x77, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x4f, 0x74, 0x70, 0x53, 0x63, + 0x72, 0x65, 0x65, 0x6e, 0x12, 0x35, 0x0a, 0x17, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x73, + 0x68, 0x6f, 0x77, 0x5f, 0x74, 0x6f, 0x74, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x53, 0x68, 0x6f, + 0x77, 0x54, 0x6f, 0x74, 0x70, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x19, + 0x0a, 0x08, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x27, 0x0a, + 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x1b, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x19, 0x61, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x14, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x0b, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x6f, 0x72, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x40, 0x0a, 0x1c, 0x61, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x1a, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x65, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x40, 0x0a, 0x0a, 0x50, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x46, 0x0a, + 0x10, 0x46, 0x67, 0x61, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x70, 0x75, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x57, 0x0a, 0x0d, 0x46, 0x67, 0x61, 0x54, 0x75, 0x70, 0x6c, + 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x95, + 0x01, 0x0a, 0x14, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x49, 0x0a, 0x11, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x67, 0x61, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x75, 0x61, 0x6c, + 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x22, 0x65, 0x0a, 0x15, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, + 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x22, 0xfc, 0x08, + 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x35, 0x0a, + 0x17, 0x69, 0x73, 0x5f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, + 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, + 0x69, 0x73, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x69, 0x73, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x62, + 0x6f, 0x6f, 0x6b, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x69, 0x73, 0x46, 0x61, 0x63, 0x65, 0x62, + 0x6f, 0x6f, 0x6b, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, + 0x35, 0x0a, 0x17, 0x69, 0x73, 0x5f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x5f, 0x6c, 0x6f, 0x67, + 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x14, 0x69, 0x73, 0x47, 0x69, 0x74, 0x68, 0x75, 0x62, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x69, 0x73, 0x5f, 0x6c, 0x69, 0x6e, + 0x6b, 0x65, 0x64, 0x69, 0x6e, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x69, 0x73, 0x4c, 0x69, 0x6e, + 0x6b, 0x65, 0x64, 0x69, 0x6e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x12, 0x33, 0x0a, 0x16, 0x69, 0x73, 0x5f, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x5f, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x13, 0x69, 0x73, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x18, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x73, + 0x63, 0x6f, 0x72, 0x64, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x73, 0x44, 0x69, 0x73, 0x63, + 0x6f, 0x72, 0x64, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, + 0x37, 0x0a, 0x18, 0x69, 0x73, 0x5f, 0x74, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x5f, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x15, 0x69, 0x73, 0x54, 0x77, 0x69, 0x74, 0x74, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x69, 0x73, 0x5f, 0x6d, + 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, 0x73, + 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x69, 0x73, 0x5f, 0x74, 0x77, 0x69, 0x74, + 0x63, 0x68, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, 0x73, 0x54, 0x77, 0x69, 0x74, 0x63, 0x68, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x17, + 0x69, 0x73, 0x5f, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x69, + 0x73, 0x52, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, + 0x6c, 0x65, 0x64, 0x12, 0x41, 0x0a, 0x1d, 0x69, 0x73, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x5f, + 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x69, 0x73, 0x45, 0x6d, + 0x61, 0x69, 0x6c, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x1f, 0x69, 0x73, 0x5f, 0x62, 0x61, 0x73, + 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x1c, 0x69, 0x73, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3c, 0x0a, + 0x1b, 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x67, 0x69, 0x63, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6c, + 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x17, 0x69, 0x73, 0x4d, 0x61, 0x67, 0x69, 0x63, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x69, + 0x73, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x5f, 0x75, 0x70, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x69, 0x73, 0x53, 0x69, 0x67, 0x6e, 0x55, + 0x70, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x69, 0x73, 0x5f, 0x73, + 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, 0x73, + 0x53, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1c, 0x69, 0x73, 0x5f, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x5f, 0x66, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x69, 0x73, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x26, 0x69, 0x73, 0x5f, 0x6d, 0x6f, 0x62, 0x69, + 0x6c, 0x65, 0x5f, 0x62, 0x61, 0x73, 0x69, 0x63, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, + 0x13, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x69, 0x73, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x42, + 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x41, 0x0a, 0x1d, 0x69, 0x73, 0x5f, + 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x1a, 0x69, 0x73, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x42, 0xbb, 0x01, 0x0a, + 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, + 0x76, 0x31, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x45, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x64, 0x65, 0x76, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x65, 0x72, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x0d, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0d, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x19, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, + 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0e, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_authorizer_v1_types_proto_rawDescOnce sync.Once + file_authorizer_v1_types_proto_rawDescData = file_authorizer_v1_types_proto_rawDesc +) + +func file_authorizer_v1_types_proto_rawDescGZIP() []byte { + file_authorizer_v1_types_proto_rawDescOnce.Do(func() { + file_authorizer_v1_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_authorizer_v1_types_proto_rawDescData) + }) + return file_authorizer_v1_types_proto_rawDescData +} + +var file_authorizer_v1_types_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_authorizer_v1_types_proto_goTypes = []any{ + (*User)(nil), // 0: authorizer.v1.User + (*AuthResponse)(nil), // 1: authorizer.v1.AuthResponse + (*Permission)(nil), // 2: authorizer.v1.Permission + (*FgaRelationInput)(nil), // 3: authorizer.v1.FgaRelationInput + (*FgaTupleInput)(nil), // 4: authorizer.v1.FgaTupleInput + (*PermissionCheckInput)(nil), // 5: authorizer.v1.PermissionCheckInput + (*PermissionCheckResult)(nil), // 6: authorizer.v1.PermissionCheckResult + (*Meta)(nil), // 7: authorizer.v1.Meta + (*v1.AppData)(nil), // 8: authorizer.common.v1.AppData +} +var file_authorizer_v1_types_proto_depIdxs = []int32{ + 8, // 0: authorizer.v1.User.app_data:type_name -> authorizer.common.v1.AppData + 0, // 1: authorizer.v1.AuthResponse.user:type_name -> authorizer.v1.User + 4, // 2: authorizer.v1.PermissionCheckInput.contextual_tuples:type_name -> authorizer.v1.FgaTupleInput + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_authorizer_v1_types_proto_init() } +func file_authorizer_v1_types_proto_init() { + if File_authorizer_v1_types_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_authorizer_v1_types_proto_rawDesc, + NumEnums: 0, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_authorizer_v1_types_proto_goTypes, + DependencyIndexes: file_authorizer_v1_types_proto_depIdxs, + MessageInfos: file_authorizer_v1_types_proto_msgTypes, + }.Build() + File_authorizer_v1_types_proto = out.File + file_authorizer_v1_types_proto_rawDesc = nil + file_authorizer_v1_types_proto_goTypes = nil + file_authorizer_v1_types_proto_depIdxs = nil +} diff --git a/gen/openapi/authorizer.swagger.json b/gen/openapi/authorizer.swagger.json new file mode 100644 index 00000000..70c5cecb --- /dev/null +++ b/gen/openapi/authorizer.swagger.json @@ -0,0 +1,1523 @@ +{ + "swagger": "2.0", + "info": { + "title": "authorizer/common/v1/annotations.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "AuthorizerService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/check_permissions": { + "post": { + "summary": "CheckPermissions evaluates one or more fine-grained permission checks\n(\"does the subject have \u003crelation\u003e on \u003cobject\u003e?\") in a single call and\nreturns one result per check, in order. The subject defaults to the\nauthenticated caller; an explicit `user` is honored only for super-admins\nor when it equals the caller's own subject. Fail-closed.", + "operationId": "AuthorizerService_CheckPermissions", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CheckPermissionsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CheckPermissionsRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/deactivate_account": { + "post": { + "summary": "DeactivateAccount soft-deletes the caller's account and revokes all\nrefresh tokens as a side effect. OAuth has no concept of account\ndeactivation; this is the typed equivalent of SCIM PATCH active=false.", + "operationId": "AuthorizerService_DeactivateAccount", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeactivateAccountResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1DeactivateAccountRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/forgot_password": { + "post": { + "operationId": "AuthorizerService_ForgotPassword", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ForgotPasswordResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ForgotPasswordRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/list_permissions": { + "post": { + "summary": "ListPermissions enumerates what the subject can access. With both\n`relation` and `object_type` set it answers \"which \u003cobject_type\u003es can I\n\u003crelation\u003e?\"; either or both filters may be omitted to enumerate every\nmatching (type, relation) pair of the active model. Results are capped;\n`truncated` reports when more permissions exist. Subject resolution\nfollows the same rules as CheckPermissions.", + "operationId": "AuthorizerService_ListPermissions", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListPermissionsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ListPermissionsRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/login": { + "post": { + "summary": "Login authenticates with email/phone + password.", + "operationId": "AuthorizerService_Login", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1LoginResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1LoginRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/logout": { + "post": { + "summary": "Logout ends the caller's current session. POST, not GET: logout mutates\nserver state (clears the session) and is audit-logged, so it must not be\na \"safe\" method per RFC 9110 §9.2.1 — GET would also expose it to CSRF.", + "operationId": "AuthorizerService_Logout", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1LogoutResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/magic_link_login": { + "post": { + "summary": "MagicLinkLogin dispatches a passwordless email link. The clicked link\nhits the existing /verify_email browser handler.", + "operationId": "AuthorizerService_MagicLinkLogin", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1MagicLinkLoginResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1MagicLinkLoginRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/meta": { + "get": { + "summary": "Meta returns server feature flags. No auth required.", + "operationId": "AuthorizerService_Meta", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1MetaResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/profile": { + "get": { + "summary": "Profile returns the authenticated user.", + "operationId": "AuthorizerService_Profile", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ProfileResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/resend_otp": { + "post": { + "operationId": "AuthorizerService_ResendOtp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ResendOtpResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ResendOtpRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/resend_verify_email": { + "post": { + "operationId": "AuthorizerService_ResendVerifyEmail", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ResendVerifyEmailResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ResendVerifyEmailRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/reset_password": { + "post": { + "operationId": "AuthorizerService_ResetPassword", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ResetPasswordResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ResetPasswordRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/revoke": { + "post": { + "summary": "Revoke invalidates a refresh token. Typed mirror of RFC 7009.", + "operationId": "AuthorizerService_Revoke", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1RevokeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1RevokeRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/session": { + "post": { + "summary": "Session returns the AuthResponse bound to the caller's cookie/bearer.\nNOT exposed as an MCP tool — SessionResponse carries access_token,\nrefresh_token, id_token, authenticator_secret, and recovery codes,\nnone of which should land in an LLM transcript. (Security audit C1.)", + "operationId": "AuthorizerService_Session", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SessionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1SessionRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/signup": { + "post": { + "summary": "Signup registers a new user. Public; requires sign-up enabled in config.\nReturns AuthResponse with tokens + (browser callers) Set-Cookie headers.", + "operationId": "AuthorizerService_Signup", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1SignupResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1SignupRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/update_profile": { + "post": { + "operationId": "AuthorizerService_UpdateProfile", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateProfileResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1UpdateProfileRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/validate_jwt_token": { + "post": { + "operationId": "AuthorizerService_ValidateJwtToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ValidateJwtTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ValidateJwtTokenRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/validate_session": { + "post": { + "operationId": "AuthorizerService_ValidateSession", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ValidateSessionResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ValidateSessionRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/verify_email": { + "post": { + "operationId": "AuthorizerService_VerifyEmail", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1VerifyEmailResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1VerifyEmailRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + }, + "/v1/verify_otp": { + "post": { + "operationId": "AuthorizerService_VerifyOtp", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1VerifyOtpResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1VerifyOtpRequest" + } + } + ], + "tags": [ + "AuthorizerService" + ] + } + } + }, + "definitions": { + "authorizerv1Meta": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "is_google_login_enabled": { + "type": "boolean" + }, + "is_facebook_login_enabled": { + "type": "boolean" + }, + "is_github_login_enabled": { + "type": "boolean" + }, + "is_linkedin_login_enabled": { + "type": "boolean" + }, + "is_apple_login_enabled": { + "type": "boolean" + }, + "is_discord_login_enabled": { + "type": "boolean" + }, + "is_twitter_login_enabled": { + "type": "boolean" + }, + "is_microsoft_login_enabled": { + "type": "boolean" + }, + "is_twitch_login_enabled": { + "type": "boolean" + }, + "is_roblox_login_enabled": { + "type": "boolean" + }, + "is_email_verification_enabled": { + "type": "boolean" + }, + "is_basic_authentication_enabled": { + "type": "boolean" + }, + "is_magic_link_login_enabled": { + "type": "boolean" + }, + "is_sign_up_enabled": { + "type": "boolean" + }, + "is_strong_password_enabled": { + "type": "boolean" + }, + "is_multi_factor_auth_enabled": { + "type": "boolean" + }, + "is_mobile_basic_authentication_enabled": { + "type": "boolean" + }, + "is_phone_verification_enabled": { + "type": "boolean" + } + }, + "description": "Meta mirrors the GraphQL Meta type — server feature flags + provider\navailability, returned by the Meta query." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "protobufNullValue": { + "type": "string", + "enum": [ + "NULL_VALUE" + ], + "default": "NULL_VALUE", + "description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\nThe JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value." + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1AppData": { + "type": "object", + "properties": { + "value": { + "type": "object" + } + }, + "description": "AppData is a free-form key/value bag stored against a user. Mirrors the\nGraphQL `Map` scalar. Values are JSON-typed (string, number, bool, null,\nnested object, nested array) to match what the existing storage layer\naccepts." + }, + "v1AuthResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "should_show_email_otp_screen": { + "type": "boolean" + }, + "should_show_mobile_otp_screen": { + "type": "boolean" + }, + "should_show_totp_screen": { + "type": "boolean" + }, + "access_token": { + "type": "string" + }, + "id_token": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "expires_in": { + "type": "string", + "format": "int64" + }, + "user": { + "$ref": "#/definitions/v1User" + }, + "authenticator_scanner_image": { + "type": "string", + "description": "TOTP enrolment artifacts (populated only when this AuthResponse\ninitiates TOTP setup; one-time, never re-shown)." + }, + "authenticator_secret": { + "type": "string" + }, + "authenticator_recovery_codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "description": "AuthResponse mirrors the GraphQL AuthResponse type. Returned (wrapped) by\nevery method that produces a session: Signup, Login, MagicLinkLogin,\nVerifyEmail, VerifyOtp, Session." + }, + "v1CheckPermissionsRequest": { + "type": "object", + "properties": { + "checks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1PermissionCheckInput" + }, + "description": "The checks to evaluate; at least one, at most 100." + }, + "user": { + "type": "string", + "description": "Optional explicit subject (\"type:id\", or a bare id treated as\n\"user:\u003cid\u003e\"). Honored only for super-admins or when it equals the\ncaller's own token subject; anything else is rejected." + } + } + }, + "v1CheckPermissionsResponse": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1PermissionCheckResult" + }, + "description": "One result per supplied check, in order." + } + } + }, + "v1DeactivateAccountRequest": { + "type": "object" + }, + "v1DeactivateAccountResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1FgaRelationInput": { + "type": "object", + "properties": { + "relation": { + "type": "string" + }, + "object": { + "type": "string" + } + }, + "description": "FgaRelationInput is a (relation, object) requirement evaluated against the\nauthenticated caller during Session / ValidateJwtToken / ValidateSession.\nAND semantics, fail-closed. Mirrors the GraphQL FgaRelationInput type." + }, + "v1FgaTupleInput": { + "type": "object", + "properties": { + "user": { + "type": "string" + }, + "relation": { + "type": "string" + }, + "object": { + "type": "string" + } + }, + "description": "FgaTupleInput is one relationship tuple supplied as request-scoped context\nfor a permission check; contextual tuples are never persisted. Mirrors the\nGraphQL FgaTupleInput type." + }, + "v1ForgotPasswordRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "state": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + } + }, + "v1ForgotPasswordResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "should_show_mobile_otp_screen": { + "type": "boolean", + "description": "For SMS-driven flows the UI may need to render an OTP entry screen." + } + } + }, + "v1ListPermissionsRequest": { + "type": "object", + "properties": { + "relation": { + "type": "string", + "description": "Optional relation filter (e.g. \"can_view\")." + }, + "object_type": { + "type": "string", + "description": "Optional object-type filter (e.g. \"document\")." + }, + "user": { + "type": "string", + "description": "Optional explicit subject; same trust rules as CheckPermissionsRequest." + } + } + }, + "v1ListPermissionsResponse": { + "type": "object", + "properties": { + "objects": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Distinct fully-qualified object ids the subject can access." + }, + "permissions": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Permission" + }, + "description": "The (object, relation) detail — relevant when no relation filter was\nsupplied." + }, + "truncated": { + "type": "boolean", + "description": "True when the result was capped (1000 entries) and more permissions\nexist." + } + } + }, + "v1LoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "password": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "state": { + "type": "string" + } + } + }, + "v1LoginResponse": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/v1AuthResponse" + } + } + }, + "v1LogoutResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1MagicLinkLoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "state": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + } + }, + "v1MagicLinkLoginResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1MetaResponse": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/definitions/authorizerv1Meta" + } + } + }, + "v1Permission": { + "type": "object", + "properties": { + "object": { + "type": "string" + }, + "relation": { + "type": "string" + } + }, + "description": "Permission is one (object, relation) pair the subject holds: \"subject has\n`relation` on `object`\". Mirrors the GraphQL Permission type." + }, + "v1PermissionCheckInput": { + "type": "object", + "properties": { + "relation": { + "type": "string" + }, + "object": { + "type": "string" + }, + "contextual_tuples": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1FgaTupleInput" + } + } + }, + "description": "PermissionCheckInput is one permission to evaluate: \"does the subject have\n\u003crelation\u003e on \u003cobject\u003e?\". Mirrors the GraphQL PermissionCheckInput type." + }, + "v1PermissionCheckResult": { + "type": "object", + "properties": { + "relation": { + "type": "string" + }, + "object": { + "type": "string" + }, + "allowed": { + "type": "boolean" + } + }, + "description": "PermissionCheckResult is the outcome of one permission check, echoing the\nchecked pair so batch results are self-describing (and positionally\naligned). Mirrors the GraphQL PermissionCheckResult type." + }, + "v1ProfileResponse": { + "type": "object", + "properties": { + "user": { + "$ref": "#/definitions/v1User" + } + } + }, + "v1ResendOtpRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "state": { + "type": "string" + } + } + }, + "v1ResendOtpResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1ResendVerifyEmailRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "state": { + "type": "string" + } + } + }, + "v1ResendVerifyEmailResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1ResetPasswordRequest": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "For email flows: the token from the reset email. For SMS flows: the OTP." + }, + "otp": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "password": { + "type": "string" + }, + "confirm_password": { + "type": "string" + } + } + }, + "v1ResetPasswordResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1RevokeRequest": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string" + } + } + }, + "v1RevokeResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1SessionRequest": { + "type": "object", + "properties": { + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "state": { + "type": "string" + }, + "required_relations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1FgaRelationInput" + }, + "title": "Optional fine-grained authorization gate: each (relation, object) is\nchecked against the authenticated caller with AND semantics, fail-closed.\nRequires fine-grained authorization enabled" + } + } + }, + "v1SessionResponse": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/v1AuthResponse" + } + } + }, + "v1SignupRequest": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "password": { + "type": "string" + }, + "confirm_password": { + "type": "string" + }, + "given_name": { + "type": "string" + }, + "family_name": { + "type": "string" + }, + "middle_name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "birthdate": { + "type": "string" + }, + "picture": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "redirect_uri": { + "type": "string" + }, + "is_multi_factor_auth_enabled": { + "type": "boolean" + }, + "state": { + "type": "string" + }, + "app_data": { + "$ref": "#/definitions/v1AppData" + } + } + }, + "v1SignupResponse": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/v1AuthResponse" + } + } + }, + "v1UpdateProfileRequest": { + "type": "object", + "properties": { + "old_password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "confirm_new_password": { + "type": "string" + }, + "email": { + "type": "string" + }, + "given_name": { + "type": "string" + }, + "family_name": { + "type": "string" + }, + "middle_name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "gender": { + "type": "string" + }, + "birthdate": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "picture": { + "type": "string" + }, + "is_multi_factor_auth_enabled": { + "type": "boolean" + }, + "app_data": { + "$ref": "#/definitions/v1AppData" + } + } + }, + "v1UpdateProfileResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "v1User": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string", + "description": "Either email or phone_number is always present." + }, + "email_verified": { + "type": "boolean" + }, + "signup_methods": { + "type": "string" + }, + "given_name": { + "type": "string" + }, + "family_name": { + "type": "string" + }, + "middle_name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "preferred_username": { + "type": "string", + "description": "Defaults to email when unset." + }, + "gender": { + "type": "string" + }, + "birthdate": { + "type": "string" + }, + "phone_number": { + "type": "string" + }, + "phone_number_verified": { + "type": "boolean" + }, + "picture": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string", + "format": "int64" + }, + "updated_at": { + "type": "string", + "format": "int64" + }, + "revoked_timestamp": { + "type": "string", + "format": "int64" + }, + "is_multi_factor_auth_enabled": { + "type": "boolean" + }, + "app_data": { + "$ref": "#/definitions/v1AppData", + "description": "Free-form key/value bag — same as GraphQL `app_data: Map`." + } + }, + "description": "User mirrors the GraphQL User type. Returned by Profile and embedded in\nAuthResponse." + }, + "v1ValidateJwtTokenRequest": { + "type": "object", + "properties": { + "token_type": { + "type": "string" + }, + "token": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_relations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1FgaRelationInput" + }, + "description": "Optional fine-grained authorization gate (AND semantics, fail-closed)." + } + } + }, + "v1ValidateJwtTokenResponse": { + "type": "object", + "properties": { + "is_valid": { + "type": "boolean" + }, + "claims": { + "$ref": "#/definitions/v1AppData", + "description": "Free-form JWT claims (matches GraphQL ValidateJWTTokenResponse.claims)." + } + } + }, + "v1ValidateSessionRequest": { + "type": "object", + "properties": { + "cookie": { + "type": "string" + }, + "roles": { + "type": "array", + "items": { + "type": "string" + } + }, + "required_relations": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1FgaRelationInput" + }, + "description": "Optional fine-grained authorization gate (AND semantics, fail-closed)." + } + } + }, + "v1ValidateSessionResponse": { + "type": "object", + "properties": { + "is_valid": { + "type": "boolean" + }, + "user": { + "$ref": "#/definitions/v1User" + } + } + }, + "v1VerifyEmailRequest": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "state": { + "type": "string" + } + } + }, + "v1VerifyEmailResponse": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/v1AuthResponse" + } + } + }, + "v1VerifyOtpRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Exactly one of email / phone_number is required." + }, + "phone_number": { + "type": "string" + }, + "otp": { + "type": "string" + }, + "is_totp": { + "type": "boolean" + }, + "state": { + "type": "string" + } + } + }, + "v1VerifyOtpResponse": { + "type": "object", + "properties": { + "auth": { + "$ref": "#/definitions/v1AuthResponse" + } + } + } + } +} diff --git a/gen/openapi/openapi.go b/gen/openapi/openapi.go new file mode 100644 index 00000000..8e113d41 --- /dev/null +++ b/gen/openapi/openapi.go @@ -0,0 +1,13 @@ +// Package openapi exposes the generated OpenAPI 2.0 spec as a byte slice +// so HTTP handlers can serve it from any working directory (test, Docker +// container, etc.). The file is embedded at compile time via go:embed so +// builds fail loudly if `make proto-gen` hasn't been run. +package openapi + +import _ "embed" + +//go:embed authorizer.swagger.json +var spec []byte + +// Spec returns the embedded OpenAPI 2.0 JSON. +func Spec() []byte { return spec } diff --git a/go.mod b/go.mod index d35c2449..b75a4dca 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/authorizerdev/authorizer go 1.26.4 require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 + buf.build/go/protovalidate v1.2.0 github.com/99designs/gqlgen v0.17.73 github.com/arangodb/go-driver v1.6.0 github.com/aws/aws-sdk-go-v2 v1.41.5 @@ -20,6 +22,8 @@ require ( github.com/gocql/gocql v1.6.0 github.com/golang-jwt/jwt/v4 v4.5.2 github.com/google/uuid v1.6.0 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 + github.com/modelcontextprotocol/go-sdk v1.6.1 github.com/openfga/api/proto v0.0.0-20260319214821-f153694bfc20 github.com/openfga/language/pkg/go v0.2.1 github.com/openfga/openfga v1.17.1 @@ -37,6 +41,8 @@ require ( golang.org/x/oauth2 v0.36.0 golang.org/x/sync v0.20.0 golang.org/x/time v0.15.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa + google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 gopkg.in/mail.v2 v2.3.1 gorm.io/driver/mysql v1.5.2 @@ -98,10 +104,10 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/cel-go v0.28.1 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -143,6 +149,8 @@ require ( github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/segmentio/encoding v0.5.4 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sosodev/duration v1.3.1 // indirect @@ -159,6 +167,7 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -182,9 +191,7 @@ require ( golang.org/x/text v0.37.0 // indirect golang.org/x/tools v0.44.0 // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/grpc v1.81.1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect diff --git a/go.sum b/go.sum index 090a4440..4296b272 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1 h1:s6hzCXtND/ICdGPTMGk7C+/BFlr2Jg5GyH0NKf4XGXg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260415201107-50325440f8f2.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0= +buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4= cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -104,6 +108,8 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -254,6 +260,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -361,6 +369,8 @@ github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= +github.com/modelcontextprotocol/go-sdk v1.6.1 h1:0zOSupjKUxPKSocPT1Wtago+mUHU2/uZ4xSOY0FGReU= +github.com/modelcontextprotocol/go-sdk v1.6.1/go.mod h1:kzm3kzFL1/+AziGOE0nUs3gvPoNxMCvkxokMkuFapXQ= 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= @@ -423,6 +433,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= +github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= +github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -432,6 +444,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= @@ -494,6 +510,8 @@ github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/config/config.go b/internal/config/config.go index 5acbfcfd..20fb739c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -43,6 +43,20 @@ type Config struct { // GraphQL endpoint to prevent oversized-payload denial of service. GraphQLMaxBodyBytes int64 + // gRPC server configuration + // GRPCPort is the port the gRPC server listens on. + GRPCPort int + // EnableGRPCReflection toggles the gRPC server-reflection service. + // Default: on (matches the playground). Disable in locked-down prod. + EnableGRPCReflection bool + // GRPCTLSCert / GRPCTLSKey set the TLS material for the gRPC listener. + // When unset and GRPCInsecure is false, the server refuses to start. + GRPCTLSCert string + GRPCTLSKey string + // GRPCInsecure permits cleartext gRPC. For local dev only; production + // should always set TLS material. + GRPCInsecure bool + // Database Configurations // DatabaseType is the type of database to use DatabaseType string diff --git a/internal/cookie/cookie.go b/internal/cookie/cookie.go index dc4d1aa7..9003a517 100644 --- a/internal/cookie/cookie.go +++ b/internal/cookie/cookie.go @@ -25,38 +25,87 @@ func ParseSameSite(value string) http.SameSite { } } -// SetSession sets the session cookie in the response +// SetSession sets the session cookie in the response. func SetSession(gc *gin.Context, sessionID string, appCookieSecure bool, sameSite http.SameSite) { - secure := appCookieSecure - httpOnly := true - hostname := parsers.GetHost(gc) + for _, c := range BuildSessionCookies(parsers.GetHost(gc), sessionID, appCookieSecure, sameSite) { + gc.SetSameSite(c.SameSite) + gc.SetCookie(c.Name, c.Value, c.MaxAge, c.Path, c.Domain, c.Secure, c.HttpOnly) + } +} + +// BuildSessionCookies returns the pair of session cookies (host-scoped and +// domain-scoped) to set on the response. Transport-agnostic so non-gin +// callers (the service layer, gRPC handlers) can produce them as side-effects. +func BuildSessionCookies(hostname, sessionID string, appCookieSecure bool, sameSite http.SameSite) []*http.Cookie { host, _ := parsers.GetHostParts(hostname) domain := parsers.GetDomainName(hostname) if domain != "localhost" { domain = "." + domain } - - gc.SetSameSite(sameSite) day := 60 * 60 * 24 - - gc.SetCookie(constants.AppCookieName+"_session", sessionID, day, "/", host, secure, httpOnly) - gc.SetCookie(constants.AppCookieName+"_session_domain", sessionID, day, "/", domain, secure, httpOnly) + return []*http.Cookie{ + { + Name: constants.AppCookieName + "_session", + Value: sessionID, + MaxAge: day, + Path: "/", + Domain: host, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + { + Name: constants.AppCookieName + "_session_domain", + Value: sessionID, + MaxAge: day, + Path: "/", + Domain: domain, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + } } -// DeleteSession sets session cookies to expire +// DeleteSession sets session cookies to expire. func DeleteSession(gc *gin.Context, appCookieSecure bool, sameSite http.SameSite) { - secure := appCookieSecure - httpOnly := true - hostname := parsers.GetHost(gc) + for _, c := range BuildDeleteSessionCookies(parsers.GetHost(gc), appCookieSecure, sameSite) { + gc.SetSameSite(c.SameSite) + gc.SetCookie(c.Name, c.Value, c.MaxAge, c.Path, c.Domain, c.Secure, c.HttpOnly) + } +} + +// BuildDeleteSessionCookies returns the pair of zero-value, expired session +// cookies that cause browsers to delete the host-scoped and domain-scoped +// session cookies. Transport-agnostic mirror of DeleteSession. +func BuildDeleteSessionCookies(hostname string, appCookieSecure bool, sameSite http.SameSite) []*http.Cookie { host, _ := parsers.GetHostParts(hostname) domain := parsers.GetDomainName(hostname) if domain != "localhost" { domain = "." + domain } - - gc.SetSameSite(sameSite) - gc.SetCookie(constants.AppCookieName+"_session", "", -1, "/", host, secure, httpOnly) - gc.SetCookie(constants.AppCookieName+"_session_domain", "", -1, "/", domain, secure, httpOnly) + return []*http.Cookie{ + { + Name: constants.AppCookieName + "_session", + Value: "", + MaxAge: -1, + Path: "/", + Domain: host, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + { + Name: constants.AppCookieName + "_session_domain", + Value: "", + MaxAge: -1, + Path: "/", + Domain: domain, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + } } // GetSession gets the session cookie from context diff --git a/internal/cookie/cookie_test.go b/internal/cookie/cookie_test.go new file mode 100644 index 00000000..e788faa5 --- /dev/null +++ b/internal/cookie/cookie_test.go @@ -0,0 +1,91 @@ +package cookie + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/internal/constants" +) + +func TestBuildSessionCookies(t *testing.T) { + tests := []struct { + name string + hostname string + secure bool + sameSite http.SameSite + wantDomain string // expected `.example.com`-style domain on the domain-scoped cookie + }{ + {"production https", "https://auth.example.com", true, http.SameSiteNoneMode, ".example.com"}, + {"localhost dev", "http://localhost:8080", false, http.SameSiteLaxMode, "localhost"}, + {"subdomain", "https://auth.svc.example.com", true, http.SameSiteStrictMode, ".example.com"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cookies := BuildSessionCookies(tt.hostname, "session-id", tt.secure, tt.sameSite) + require.Len(t, cookies, 2, "BuildSessionCookies must return exactly the host-scoped and domain-scoped pair") + + for _, c := range cookies { + assert.Equal(t, "session-id", c.Value) + assert.Equal(t, tt.secure, c.Secure) + assert.True(t, c.HttpOnly, "session cookies must be HttpOnly") + assert.Equal(t, "/", c.Path) + assert.Equal(t, tt.sameSite, c.SameSite) + assert.Equal(t, 24*60*60, c.MaxAge, "session cookie MaxAge must be 1 day") + } + + // Sanity-check cookie names. + assert.Equal(t, constants.AppCookieName+"_session", cookies[0].Name) + assert.Equal(t, constants.AppCookieName+"_session_domain", cookies[1].Name) + // Domain-scoped cookie picks up the apex. + assert.Equal(t, tt.wantDomain, cookies[1].Domain) + }) + } +} + +func TestBuildMfaSessionCookies(t *testing.T) { + cookies := BuildMfaSessionCookies("https://auth.example.com", "mfa-id", true) + require.Len(t, cookies, 2) + for _, c := range cookies { + assert.Equal(t, "mfa-id", c.Value) + assert.True(t, c.Secure) + assert.True(t, c.HttpOnly) + assert.Equal(t, http.SameSiteNoneMode, c.SameSite, "secure → SameSite=None") + assert.Equal(t, 60, c.MaxAge, "MFA cookies are short-lived (60s)") + } + assert.Equal(t, constants.MfaCookieName+"_session", cookies[0].Name) + assert.Equal(t, constants.MfaCookieName+"_session_domain", cookies[1].Name) +} + +func TestBuildMfaSessionCookies_InsecureLaxSameSite(t *testing.T) { + cookies := BuildMfaSessionCookies("http://localhost:8080", "mfa-id", false) + require.Len(t, cookies, 2) + for _, c := range cookies { + assert.False(t, c.Secure) + // Insecure → SameSite=Lax (so cross-site flows still complete when not behind TLS). + // Verified against the original SetMfaSession behaviour: this is intentional. + assert.Equal(t, http.SameSiteLaxMode, c.SameSite) + } +} + +func TestParseSameSite(t *testing.T) { + tests := []struct { + in string + want http.SameSite + }{ + {"none", http.SameSiteNoneMode}, + {"NONE", http.SameSiteNoneMode}, + {"strict", http.SameSiteStrictMode}, + {"lax", http.SameSiteLaxMode}, + {"", http.SameSiteLaxMode}, // unknown defaults to Lax + {"garbage", http.SameSiteLaxMode}, + {" none ", http.SameSiteNoneMode}, + } + for _, tt := range tests { + t.Run(tt.in, func(t *testing.T) { + assert.Equal(t, tt.want, ParseSameSite(tt.in)) + }) + } +} diff --git a/internal/cookie/mfa_session.go b/internal/cookie/mfa_session.go index bb09702e..3ebe8bb2 100644 --- a/internal/cookie/mfa_session.go +++ b/internal/cookie/mfa_session.go @@ -10,33 +10,55 @@ import ( "github.com/authorizerdev/authorizer/internal/parsers" ) -// SetMfaSession sets the mfa session cookie in the response +// SetMfaSession sets the mfa session cookie in the response. func SetMfaSession(gc *gin.Context, sessionID string, appCookieSecure bool) { - secure := appCookieSecure - httpOnly := true - hostname := parsers.GetHost(gc) + for _, c := range BuildMfaSessionCookies(parsers.GetHost(gc), sessionID, appCookieSecure) { + gc.SetSameSite(c.SameSite) + gc.SetCookie(c.Name, c.Value, c.MaxAge, c.Path, c.Domain, c.Secure, c.HttpOnly) + } +} + +// BuildMfaSessionCookies returns the MFA session cookies (host-scoped and +// domain-scoped) to set on the response. Transport-agnostic mirror of +// SetMfaSession. +// +// SameSite policy mirrors the gin path: Lax when insecure (so cross-site UI +// can still complete the flow), None when secure. See the SetMfaSession +// comment for the historical reasoning and the configurability TODO. +func BuildMfaSessionCookies(hostname, sessionID string, appCookieSecure bool) []*http.Cookie { host, _ := parsers.GetHostParts(hostname) domain := parsers.GetDomainName(hostname) if domain != "localhost" { domain = "." + domain } - - // Since app cookie can come from cross site it becomes important to set this in lax mode when insecure. - // Example person using custom UI on their app domain and making request to authorizer domain. - // For more information check: - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite - // https://github.com/gin-gonic/gin/blob/master/context.go#L86 - // TODO add ability to configure sameSite (none / lax / strict) via config + sameSite := http.SameSiteNoneMode if !appCookieSecure { - gc.SetSameSite(http.SameSiteLaxMode) - } else { - gc.SetSameSite(http.SameSiteNoneMode) + sameSite = http.SameSiteLaxMode } // TODO allow configuring cookie max-age via config age := 60 - - gc.SetCookie(constants.MfaCookieName+"_session", sessionID, age, "/", host, secure, httpOnly) - gc.SetCookie(constants.MfaCookieName+"_session_domain", sessionID, age, "/", domain, secure, httpOnly) + return []*http.Cookie{ + { + Name: constants.MfaCookieName + "_session", + Value: sessionID, + MaxAge: age, + Path: "/", + Domain: host, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + { + Name: constants.MfaCookieName + "_session_domain", + Value: sessionID, + MaxAge: age, + Path: "/", + Domain: domain, + Secure: appCookieSecure, + HttpOnly: true, + SameSite: sameSite, + }, + } } // DeleteMfaSession deletes the mfa session cookies to expire diff --git a/internal/e2e/smoke_test.go b/internal/e2e/smoke_test.go new file mode 100644 index 00000000..97887d63 --- /dev/null +++ b/internal/e2e/smoke_test.go @@ -0,0 +1,442 @@ +//go:build smoke + +// Package e2e holds release smoke tests: black-box checks that build the real +// `authorizer` binary, boot it as a subprocess, and exercise every public API +// surface (GraphQL, REST, gRPC, MCP) end to end — including an authenticated +// fine-grained-authorization decision on each surface. +// +// They are deliberately excluded from the regular unit/integration runs (build +// tag `smoke`) because they compile the binary and bind real ports. Run them +// with: +// +// make smoke +// +// CI runs them on every release (see .github/workflows/release.yaml). +package e2e + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "net/http/cookiejar" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// Fixed credentials for the smoke instance. Test-only values. +const ( + smokeJWTSecret = "smoke-jwt-secret-0123456789" + smokeAdminSecret = "smoke-admin-secret" + smokeClientID = "11111111-2222-3333-4444-555555555555" + smokeClientSecret = "smoke-client-secret" + smokeUserEmail = "smoke@test.dev" + smokeUserPassword = "Smoke-Pass-123!" + + // fgaModelDSL is the minimal OpenFGA model the scenario authorizes + // against: a user can be a viewer of a document. + fgaModelDSL = "model\n schema 1.1\ntype user\ntype document\n relations\n define viewer: [user]" +) + +// TestReleaseSmoke is the release gate: one scenario, four surfaces. +// +// 1. Build the binary and boot it (sqlite storage; FGA auto-derives onto the +// same sqlite file so the MCP subprocess can share it later). +// 2. Seed via GraphQL: admin login, FGA model + tuple, user signup. +// 3. Assert the same check_permissions / list_permissions decision on +// GraphQL, REST, and gRPC, plus REST fail-closed and validation paths. +// 4. Stop the server and drive the `authorizer mcp` stdio subcommand through +// a real MCP handshake with the minted bearer token. +func TestReleaseSmoke(t *testing.T) { + bin := buildBinary(t) + dbPath := filepath.Join(t.TempDir(), "smoke.db") + httpPort, metricsPort, grpcPort := freePort(t), freePort(t), freePort(t) + baseURL := fmt.Sprintf("http://127.0.0.1:%d", httpPort) + + serverArgs := []string{ + "--database-type=sqlite", "--database-url=" + dbPath, + "--jwt-type=HS256", "--jwt-secret=" + smokeJWTSecret, + "--admin-secret=" + smokeAdminSecret, + "--client-id=" + smokeClientID, "--client-secret=" + smokeClientSecret, + fmt.Sprintf("--http-port=%d", httpPort), + fmt.Sprintf("--metrics-port=%d", metricsPort), + fmt.Sprintf("--grpc-port=%d", grpcPort), + } + stopServer := startServer(t, bin, serverArgs, baseURL) + + gql := newGraphQLClient(t, baseURL) + + // --- Seed: admin session, FGA model, user, tuple -------------------- + gql.mutate(t, `mutation { _admin_login(params:{admin_secret:"`+smokeAdminSecret+`"}) { message } }`) + gql.mutate(t, `mutation { _fga_write_model(params:{dsl:"`+strings.ReplaceAll(fgaModelDSL, "\n", `\n`)+`"}) { id } }`) + + signup := gql.mutate(t, `mutation { signup(params:{email:"`+smokeUserEmail+`", password:"`+smokeUserPassword+`", confirm_password:"`+smokeUserPassword+`"}) { access_token user { id } } }`) + token := signup["signup"].(map[string]any)["access_token"].(string) + userID := signup["signup"].(map[string]any)["user"].(map[string]any)["id"].(string) + require.NotEmpty(t, token) + require.NotEmpty(t, userID) + + gql.mutate(t, `mutation { _fga_write_tuples(params:{tuples:[{user:"user:`+userID+`", relation:"viewer", object:"document:readme"}]}) { message } }`) + + // --- Surface 1: GraphQL --------------------------------------------- + t.Run("graphql check_permissions", func(t *testing.T) { + data := gql.query(t, token, + `query { check_permissions(params:{checks:[{relation:"viewer", object:"document:readme"},{relation:"viewer", object:"document:secret"}]}) { results { object allowed } } }`) + results := data["check_permissions"].(map[string]any)["results"].([]any) + require.Len(t, results, 2) + assert.True(t, results[0].(map[string]any)["allowed"].(bool), "viewer on document:readme") + assert.False(t, results[1].(map[string]any)["allowed"].(bool), "viewer on document:secret") + }) + + // --- Surface 2: REST (/v1 via grpc-gateway) ------------------------- + t.Run("rest check_permissions", func(t *testing.T) { + var out struct { + Results []struct { + Object string `json:"object"` + Allowed bool `json:"allowed"` + } `json:"results"` + } + status := restJSON(t, baseURL, "/v1/check_permissions", token, + `{"checks":[{"relation":"viewer","object":"document:readme"},{"relation":"viewer","object":"document:secret"}]}`, &out) + require.Equal(t, http.StatusOK, status) + require.Len(t, out.Results, 2) + assert.True(t, out.Results[0].Allowed) + assert.False(t, out.Results[1].Allowed) + }) + + t.Run("rest list_permissions", func(t *testing.T) { + var out struct { + Objects []string `json:"objects"` + Truncated bool `json:"truncated"` + } + status := restJSON(t, baseURL, "/v1/list_permissions", token, `{}`, &out) + require.Equal(t, http.StatusOK, status) + assert.Equal(t, []string{"document:readme"}, out.Objects) + assert.False(t, out.Truncated) + }) + + t.Run("rest fail-closed and validation", func(t *testing.T) { + var env struct { + Code string `json:"code"` + } + // No auth -> 401 unauthenticated. + status := restJSON(t, baseURL, "/v1/check_permissions", "", + `{"checks":[{"relation":"viewer","object":"document:readme"}]}`, &env) + assert.Equal(t, http.StatusUnauthorized, status) + assert.Equal(t, "unauthenticated", env.Code) + // Empty checks -> 400 invalid_argument (protovalidate min_items=1). + status = restJSON(t, baseURL, "/v1/check_permissions", token, `{"checks":[]}`, &env) + assert.Equal(t, http.StatusBadRequest, status) + assert.Equal(t, "invalid_argument", env.Code) + }) + + // --- Surface 3: gRPC ------------------------------------------------- + t.Run("grpc check_permissions", func(t *testing.T) { + conn, err := grpc.NewClient(fmt.Sprintf("127.0.0.1:%d", grpcPort), + grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + defer conn.Close() + client := authorizerv1.NewAuthorizerServiceClient(conn) + + // Pure-gRPC callers carry the token plus the authorizer host (the + // issuer the token was minted with) as metadata. + ctx := metadata.AppendToOutgoingContext(context.Background(), + "authorization", "Bearer "+token, + "x-authorizer-url", baseURL) + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + res, err := client.CheckPermissions(ctx, &authorizerv1.CheckPermissionsRequest{ + Checks: []*authorizerv1.PermissionCheckInput{ + {Relation: "viewer", Object: "document:readme"}, + {Relation: "viewer", Object: "document:secret"}, + }}) + require.NoError(t, err) + require.Len(t, res.Results, 2) + assert.True(t, res.Results[0].Allowed) + assert.False(t, res.Results[1].Allowed) + + list, err := client.ListPermissions(ctx, &authorizerv1.ListPermissionsRequest{}) + require.NoError(t, err) + assert.Equal(t, []string{"document:readme"}, list.Objects) + }) + + // --- Surface 4: MCP (stdio subprocess) ------------------------------- + // The MCP subcommand is a separate process sharing the sqlite store, so + // stop the server first to avoid two writers on one sqlite file. + stopServer() + + t.Run("mcp stdio", func(t *testing.T) { + mcpArgs := []string{"mcp", + "--database-type=sqlite", "--database-url=" + dbPath, + "--jwt-type=HS256", "--jwt-secret=" + smokeJWTSecret, + "--admin-secret=" + smokeAdminSecret, + "--client-id=" + smokeClientID, "--client-secret=" + smokeClientSecret, + "--mcp-bearer=" + token, + "--mcp-authorizer-url=" + baseURL, + } + mcp := startMCP(t, bin, mcpArgs) + + init := mcp.call(t, "initialize", map[string]any{ + "protocolVersion": "2025-06-18", + "capabilities": map[string]any{}, + "clientInfo": map[string]any{"name": "release-smoke", "version": "1.0"}, + }) + require.Equal(t, "authorizer", init["serverInfo"].(map[string]any)["name"]) + mcp.notify(t, "notifications/initialized") + + tools := mcp.call(t, "tools/list", nil) + names := map[string]bool{} + for _, tool := range tools["tools"].([]any) { + names[tool.(map[string]any)["name"].(string)] = true + } + for _, want := range []string{"meta", "profile", "check_permissions", "list_permissions"} { + assert.True(t, names[want], "tool %q must be exposed", want) + } + assert.False(t, names["permissions"], "legacy permissions tool must be gone") + + check := mcp.toolCall(t, "check_permissions", map[string]any{ + "checks": []any{ + map[string]any{"relation": "viewer", "object": "document:readme"}, + map[string]any{"relation": "viewer", "object": "document:secret"}, + }}) + var checkOut struct { + Results []struct { + Allowed bool `json:"allowed"` + } `json:"results"` + } + require.NoError(t, json.Unmarshal([]byte(check), &checkOut)) + require.Len(t, checkOut.Results, 2) + assert.True(t, checkOut.Results[0].Allowed) + assert.False(t, checkOut.Results[1].Allowed) + + profile := mcp.toolCall(t, "profile", map[string]any{}) + var profOut struct { + User struct { + Email string `json:"email"` + } `json:"user"` + } + require.NoError(t, json.Unmarshal([]byte(profile), &profOut)) + assert.Equal(t, smokeUserEmail, profOut.User.Email) + }) +} + +// buildBinary compiles the authorizer binary into a temp dir and returns its +// path. Building from source guarantees the smoke run tests exactly the code +// under release, not a stale artifact. +func buildBinary(t *testing.T) string { + t.Helper() + bin := filepath.Join(t.TempDir(), "authorizer") + cmd := exec.Command("go", "build", "-o", bin, ".") + cmd.Dir = repoRoot(t) + out, err := cmd.CombinedOutput() + require.NoError(t, err, "go build: %s", out) + return bin +} + +// repoRoot resolves the module root (two levels up from internal/e2e). +func repoRoot(t *testing.T) string { + t.Helper() + wd, err := os.Getwd() + require.NoError(t, err) + return filepath.Dir(filepath.Dir(wd)) +} + +// freePort reserves an ephemeral TCP port and returns it. The listener is +// closed immediately; the tiny reuse race is acceptable for tests. +func freePort(t *testing.T) int { + t.Helper() + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + port := l.Addr().(*net.TCPAddr).Port + require.NoError(t, l.Close()) + return port +} + +// startServer boots the binary, waits until /v1/meta serves, and returns a +// stop function (also registered as cleanup, safe to call twice). +func startServer(t *testing.T, bin string, args []string, baseURL string) func() { + t.Helper() + logPath := filepath.Join(t.TempDir(), "server.log") + logFile, err := os.Create(logPath) + require.NoError(t, err) + + cmd := exec.Command(bin, args...) + // The server resolves web assets (web/templates/*) relative to its + // working directory; run it from the repo root like a real deployment. + cmd.Dir = repoRoot(t) + cmd.Stdout = logFile + cmd.Stderr = logFile + require.NoError(t, cmd.Start()) + + stopped := false + stop := func() { + if stopped { + return + } + stopped = true + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + _ = logFile.Close() + } + t.Cleanup(stop) + + deadline := time.Now().Add(30 * time.Second) + for time.Now().Before(deadline) { + resp, err := http.Get(baseURL + "/v1/meta") + if err == nil { + _ = resp.Body.Close() + if resp.StatusCode == http.StatusOK { + return stop + } + } + time.Sleep(250 * time.Millisecond) + } + logs, _ := os.ReadFile(logPath) + t.Fatalf("server did not become ready; log:\n%s", logs) + return stop +} + +// graphQLClient is a minimal GraphQL-over-HTTP client with a cookie jar (the +// admin session is cookie-based) and the Origin header the CSRF middleware +// requires on state-changing requests. +type graphQLClient struct { + url string + client *http.Client +} + +func newGraphQLClient(t *testing.T, baseURL string) *graphQLClient { + t.Helper() + jar, err := cookiejar.New(nil) + require.NoError(t, err) + return &graphQLClient{url: baseURL + "/graphql", client: &http.Client{Jar: jar, Timeout: 15 * time.Second}} +} + +func (g *graphQLClient) do(t *testing.T, query, bearer string) map[string]any { + t.Helper() + body, err := json.Marshal(map[string]string{"query": query}) + require.NoError(t, err) + req, err := http.NewRequest(http.MethodPost, g.url, strings.NewReader(string(body))) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Origin", strings.TrimSuffix(g.url, "/graphql")) + if bearer != "" { + req.Header.Set("Authorization", "Bearer "+bearer) + } + resp, err := g.client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + var out struct { + Data map[string]any `json:"data"` + Errors []any `json:"errors"` + } + require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) + require.Empty(t, out.Errors, "graphql errors for %s", query) + return out.Data +} + +func (g *graphQLClient) mutate(t *testing.T, query string) map[string]any { + return g.do(t, query, "") +} + +func (g *graphQLClient) query(t *testing.T, bearer, query string) map[string]any { + return g.do(t, query, bearer) +} + +// restJSON POSTs a JSON body to a /v1 path and decodes the response into out. +// Returns the HTTP status. An empty bearer sends no Authorization header. +func restJSON(t *testing.T, baseURL, path, bearer, body string, out any) int { + t.Helper() + req, err := http.NewRequest(http.MethodPost, baseURL+path, strings.NewReader(body)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Origin", baseURL) + if bearer != "" { + req.Header.Set("Authorization", "Bearer "+bearer) + } + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.NoError(t, json.NewDecoder(resp.Body).Decode(out)) + return resp.StatusCode +} + +// mcpProc drives an `authorizer mcp` subprocess over stdio JSON-RPC, the same +// transport an MCP host (Claude Code) uses. +type mcpProc struct { + cmd *exec.Cmd + stdin *json.Encoder + stdout *bufio.Scanner + nextID int +} + +func startMCP(t *testing.T, bin string, args []string) *mcpProc { + t.Helper() + cmd := exec.Command(bin, args...) + cmd.Dir = repoRoot(t) + stdin, err := cmd.StdinPipe() + require.NoError(t, err) + stdout, err := cmd.StdoutPipe() + require.NoError(t, err) + cmd.Stderr = os.Stderr + require.NoError(t, cmd.Start()) + t.Cleanup(func() { + _ = cmd.Process.Kill() + _, _ = cmd.Process.Wait() + }) + scanner := bufio.NewScanner(stdout) + scanner.Buffer(make([]byte, 1<<20), 1<<20) + return &mcpProc{cmd: cmd, stdin: json.NewEncoder(stdin), stdout: scanner} +} + +// call sends one JSON-RPC request and returns the `result` object. +func (m *mcpProc) call(t *testing.T, method string, params any) map[string]any { + t.Helper() + m.nextID++ + req := map[string]any{"jsonrpc": "2.0", "id": m.nextID, "method": method} + if params != nil { + req["params"] = params + } + require.NoError(t, m.stdin.Encode(req)) + require.True(t, m.stdout.Scan(), "mcp server closed stdout (scan err: %v)", m.stdout.Err()) + var resp struct { + Result map[string]any `json:"result"` + Error any `json:"error"` + } + require.NoError(t, json.Unmarshal(m.stdout.Bytes(), &resp)) + require.Nil(t, resp.Error, "jsonrpc error for %s", method) + return resp.Result +} + +// notify sends a JSON-RPC notification (no response expected). +func (m *mcpProc) notify(t *testing.T, method string) { + t.Helper() + require.NoError(t, m.stdin.Encode(map[string]any{"jsonrpc": "2.0", "method": method})) +} + +// toolCall invokes tools/call, asserts isError=false, and returns the text +// payload of the first content block. +func (m *mcpProc) toolCall(t *testing.T, name string, args map[string]any) string { + t.Helper() + res := m.call(t, "tools/call", map[string]any{"name": name, "arguments": args}) + isErr, _ := res["isError"].(bool) + content := res["content"].([]any) + require.NotEmpty(t, content) + text := content[0].(map[string]any)["text"].(string) + require.False(t, isErr, "tool %s returned error: %s", name, text) + return text +} diff --git a/internal/email/email.go b/internal/email/email.go index 43eea20b..0596633f 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -4,8 +4,8 @@ import ( "bytes" "context" "crypto/tls" - "strings" "html/template" + "strings" "github.com/rs/zerolog" gomail "gopkg.in/mail.v2" diff --git a/internal/gateway/mount.go b/internal/gateway/mount.go new file mode 100644 index 00000000..052eef18 --- /dev/null +++ b/internal/gateway/mount.go @@ -0,0 +1,181 @@ +// Package gateway translates REST (`/v1/*`) calls into in-process gRPC +// method invocations using grpc-gateway. The resulting http.Handler is +// mounted into the existing Gin router so middleware (CORS, security +// headers, rate limit, logging) is shared. +// +// We deliberately use the *in-process* dialer style: the gateway dials the +// running grpc.Server via bufconn rather than making a real network hop. +// This avoids the latency and TLS-cert plumbing of a loopback gRPC call, +// and removes the need to keep two ports in sync. +package gateway + +import ( + "context" + "encoding/json" + "net" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/encoding/protojson" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" + "github.com/authorizerdev/authorizer/internal/parsers" +) + +// bufconn size; large enough that in-process gateway calls never block. +const bufSize = 1 << 20 + +// Handler builds an http.Handler that translates `/v1/*` REST calls into +// gRPC calls against the supplied in-process *grpc.Server. Returns the +// handler and a cleanup function the caller invokes at shutdown. +func Handler(ctx context.Context, grpcSrv *grpc.Server) (http.Handler, func(), error) { + lis := bufconn.Listen(bufSize) + + // Serve gRPC over the bufconn in a goroutine; the existing TCP + // listener (started by grpcsrv.Server.Run) is the public entry point — + // this listener only carries in-process gateway traffic. + go func() { + _ = grpcSrv.Serve(lis) + }() + + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + _ = lis.Close() + return nil, nil, err + } + + mux := runtime.NewServeMux( + // Forward the original request's authorizer host URL to the gRPC + // layer. The in-process bufconn call carries `:authority=bufconn`, + // so without this the service layer would resolve the host as + // "http://bufconn" and JWT issuer validation would reject every + // token minted via the HTTP surface. parsers.GetHostFromRequest is + // the same spoof-hardened resolution the gin path uses; + // transport.MetaFromGRPC reads `x-authorizer-url` first. + runtime.WithMetadata(func(_ context.Context, r *http.Request) metadata.MD { + return metadata.Pairs("x-authorizer-url", parsers.GetHostFromRequest(r)) + }), + // Consistent error envelope across the REST surface (see errorHandler). + runtime.WithErrorHandler(errorHandler), + // Preserve true HTTP routing statuses (e.g. 405 for a method mismatch + // such as GET on a POST-only endpoint) instead of grpc-gateway's + // default 405->501 remap. See routingErrorHandler. + runtime.WithRoutingErrorHandler(routingErrorHandler), + // Use snake_case proto field names (UseProtoNames=true) over the + // camelCase default — keeps payloads aligned with the existing + // GraphQL surface. DiscardUnknown tolerates older clients sending + // fields that have since been removed. + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), + ) + if err := registerAll(ctx, mux, conn); err != nil { + _ = conn.Close() + _ = lis.Close() + return nil, nil, err + } + + cleanup := func() { + _ = conn.Close() + _ = lis.Close() + } + return mux, cleanup, nil +} + +// errorHandler renders REST errors in a single, stable JSON envelope across +// every /v1 endpoint: +// +// {"code": "invalid_argument", "message": "..."} +// +// instead of grpc-gateway's default `{"code": , "message": ..., "details": []}`. +// The numeric-to-HTTP-status mapping still comes from runtime.HTTPStatusFromCode +// so e.g. InvalidArgument -> 400, Unauthenticated -> 401, PermissionDenied -> +// 403, NotFound -> 404, Internal -> 500. The `code` token is snake_case to +// match the rest of the API's naming. +func errorHandler(_ context.Context, _ *runtime.ServeMux, _ runtime.Marshaler, w http.ResponseWriter, _ *http.Request, err error) { + st := status.Convert(err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(runtime.HTTPStatusFromCode(st.Code())) + _ = json.NewEncoder(w).Encode(map[string]string{ + "code": codeName(st.Code()), + "message": st.Message(), + }) +} + +// routingErrorHandler renders gateway routing failures (no matching route, +// or a method mismatch) using the same JSON envelope as errorHandler while +// keeping the correct HTTP status. grpc-gateway's default remaps 405 -> +// codes.Unimplemented -> 501, which is misleading for clients; here a GET on a +// POST-only path stays a 405. +func routingErrorHandler(_ context.Context, _ *runtime.ServeMux, _ runtime.Marshaler, w http.ResponseWriter, _ *http.Request, httpStatus int) { + var code string + switch httpStatus { + case http.StatusMethodNotAllowed: + code = "method_not_allowed" + case http.StatusNotFound: + code = "not_found" + case http.StatusBadRequest: + code = "invalid_argument" + default: + code = "internal" + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(httpStatus) + _ = json.NewEncoder(w).Encode(map[string]string{ + "code": code, + "message": http.StatusText(httpStatus), + }) +} + +// codeName maps a gRPC status code onto a stable snake_case token for the REST +// error envelope. Unmapped codes fall back to "internal". +func codeName(c codes.Code) string { + switch c { + case codes.OK: + return "ok" + case codes.InvalidArgument: + return "invalid_argument" + case codes.Unauthenticated: + return "unauthenticated" + case codes.PermissionDenied: + return "permission_denied" + case codes.NotFound: + return "not_found" + case codes.AlreadyExists: + return "already_exists" + case codes.FailedPrecondition: + return "failed_precondition" + case codes.Unavailable: + return "unavailable" + case codes.Unimplemented: + return "unimplemented" + case codes.DeadlineExceeded: + return "deadline_exceeded" + case codes.ResourceExhausted: + return "resource_exhausted" + default: + return "internal" + } +} + +func registerAll(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + // Single AuthorizerService. As more services land (admin-side ones + // that today stay GraphQL-only), add their registrar here. + return authorizerv1.RegisterAuthorizerServiceHandler(ctx, mux, conn) +} diff --git a/internal/graphql/check_permissions.go b/internal/graphql/check_permissions.go index 98f5bd1c..03d52956 100644 --- a/internal/graphql/check_permissions.go +++ b/internal/graphql/check_permissions.go @@ -2,75 +2,16 @@ package graphql import ( "context" - "fmt" - "strings" - "time" - "github.com/authorizerdev/authorizer/internal/authorization/engine" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/metrics" - "github.com/authorizerdev/authorizer/internal/refs" + "github.com/authorizerdev/authorizer/internal/service" + "github.com/authorizerdev/authorizer/internal/utils" ) -// CheckPermissions evaluates one or more permission checks ("does the subject -// have on ?") in a single call and returns one result per -// check, in order. A single check is simply a list of one. -// -// SUBJECT TRUST GATE: the subject defaults to the authenticated caller's token -// subject; an explicit `user` is honored only for super-admins or when it -// equals the caller's own subject (see resolveFgaSubject). Fail-closed: any -// engine error denies. -// Permission: authorized user. +// CheckPermissions delegates to the transport-agnostic service layer, which +// owns the subject trust gate and fail-closed semantics. func (g *graphqlProvider) CheckPermissions(ctx context.Context, params *model.CheckPermissionsInput) (*model.CheckPermissionsResponse, error) { - log := g.Log.With().Str("func", "CheckPermissions").Logger() - if g.AuthzEngine == nil { - return nil, errFgaNotEnabled - } - if params == nil || len(params.Checks) == 0 { - return nil, fmt.Errorf("at least one check is required") - } - if len(params.Checks) > maxPermissionChecks { - return nil, fmt.Errorf("too many checks: max %d per request", maxPermissionChecks) - } - subject, err := g.resolveFgaSubject(ctx, refs.StringValue(params.User)) - if err != nil { - log.Debug().Err(err).Msg("Failed to resolve subject") - return nil, err - } - requests := make([]engine.CheckRequest, 0, len(params.Checks)) - for _, c := range params.Checks { - if c == nil || strings.TrimSpace(c.Relation) == "" || strings.TrimSpace(c.Object) == "" { - return nil, fmt.Errorf("each check requires relation and object") - } - ctxTuples, err := toContextualTuples(c.ContextualTuples) - if err != nil { - return nil, err - } - requests = append(requests, engine.CheckRequest{ - User: subject, - Relation: c.Relation, - Object: c.Object, - ContextualTuples: ctxTuples, - }) - } - start := time.Now() - results, err := g.AuthzEngine.BatchCheck(ctx, requests) - metrics.ObserveFgaCheckDuration(metrics.FgaOpCheckPermissions, time.Since(start).Seconds()) - if err != nil { - // Fail closed for the whole call. - metrics.RecordFgaCheck(metrics.FgaOpCheckPermissions, metrics.FgaResultError) - log.Debug().Err(err).Msg("CheckPermissions failed; denying") - return nil, fmt.Errorf("authorization check failed") - } - out := &model.CheckPermissionsResponse{Results: make([]*model.PermissionCheckResult, 0, len(results))} - for i, r := range results { - // Record each decision so adoption/denial rates reflect every pair. - metrics.RecordFgaCheckResult(metrics.FgaOpCheckPermissions, r.Allowed) - out.Results = append(out.Results, &model.PermissionCheckResult{ - Relation: params.Checks[i].Relation, - Object: params.Checks[i].Object, - Allowed: r.Allowed, - }) - } - return out, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.CheckPermissions(ctx, service.MetaFromGin(gc), params) + return res, err } diff --git a/internal/graphql/fga.go b/internal/graphql/fga.go index 48f718d3..39e4aa53 100644 --- a/internal/graphql/fga.go +++ b/internal/graphql/fga.go @@ -1,7 +1,6 @@ package graphql import ( - "context" "errors" "fmt" "regexp" @@ -9,7 +8,6 @@ import ( "github.com/authorizerdev/authorizer/internal/authorization/engine" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/utils" ) // errFgaNotEnabled is returned by every FGA resolver when no authorization @@ -30,16 +28,6 @@ const maxFgaReadPageSize = 100 // surface, so the result set is bounded. const maxFgaListResults = 1000 -// maxPermissionChecks caps the number of checks accepted by a single -// check_permissions call. -const maxPermissionChecks = 100 - -// maxContextualTuplesPerCheck caps client-supplied contextual tuples on a -// single check. OpenFGA enforces its own per-request limit (default 100), but -// the boundary must not depend on the embedded engine's configuration — -// contextual tuples are accepted from any authenticated caller. -const maxContextualTuplesPerCheck = 100 - // resolveFgaSubject is the single, centralized trust gate for the public // permission APIs (check_permissions, list_permissions). It decides which // OpenFGA subject ("type:id") a decision is evaluated for, given the optional @@ -55,93 +43,6 @@ const maxContextualTuplesPerCheck = 100 // because honoring it would let an end user probe another subject's // access (IDOR / info disclosure). // -// TODO(phase-2 M2M): machine-to-machine / client-credentials callers should -// also be allowed to pass an explicit user once that caller type exists; -// extend the trust check here (the rule must stay centralized in this one -// helper). -func (g *graphqlProvider) resolveFgaSubject(ctx context.Context, explicitUser string) (string, error) { - gc, err := utils.GinContextFromContext(ctx) - if err != nil { - return "", err - } - explicitUser = strings.TrimSpace(explicitUser) - - // The caller's own subject, when they carry a user token/session. Resolved - // lazily-ish here because both branches may need it. - ownSubject := "" - if tokenData, terr := g.TokenProvider.GetUserIDFromSessionOrAccessToken(gc); terr == nil && strings.TrimSpace(tokenData.UserID) != "" { - ownSubject = "user:" + tokenData.UserID - } - - if explicitUser == "" { - // Default: pin to the caller's own token subject. - if ownSubject == "" { - return "", fmt.Errorf("unauthorized") - } - return ownSubject, nil - } - - subject := normalizeFgaSubject(explicitUser) - if err := validateFgaSubject(subject); err != nil { - return "", err - } - // Self-specification is always allowed: it is exactly what the token - // already proves. This keeps client code symmetric (it may always send - // `user`) while the server stays strict. The comparison is exact-string - // after outer TrimSpace + normalization — no inner-whitespace or case - // tolerance; a near-miss falls through and is rejected (fail-closed). - if subject == ownSubject { - return subject, nil - } - // Only a super-admin may evaluate a different subject. The trust level is - // derived from the admin cookie/secret — never from client input. - if g.TokenProvider.IsSuperAdmin(gc) { - return subject, nil - } - return "", fmt.Errorf("not authorized to query authorization for another subject") -} - -// normalizeFgaSubject turns a bare id into the canonical "user:" form; -// values that already carry a type ("type:id") pass through unchanged. -func normalizeFgaSubject(user string) string { - if !strings.Contains(user, ":") { - return "user:" + user - } - return user -} - -// validateFgaSubject ensures an explicitly supplied subject is in OpenFGA -// "type:id" form (both halves non-empty). It rejects usersets -// ("type:id#relation") and malformed values. -func validateFgaSubject(user string) error { - objType, objID, found := strings.Cut(user, ":") - if !found || strings.TrimSpace(objType) == "" || strings.TrimSpace(objID) == "" { - return fmt.Errorf("user must be in type:id form, got %q", user) - } - if strings.Contains(objID, "#") { - return fmt.Errorf("user must be a concrete subject in type:id form, not a userset, got %q", user) - } - return nil -} - -// toContextualTuples converts client-supplied contextual tuples. These are -// request-scoped only (never persisted) and are safe to accept from the client. -func toContextualTuples(in []*model.FgaTupleInput) ([]engine.ContextualTuple, error) { - if len(in) == 0 { - return nil, nil - } - if len(in) > maxContextualTuplesPerCheck { - return nil, fmt.Errorf("too many contextual tuples: max %d per check", maxContextualTuplesPerCheck) - } - out := make([]engine.ContextualTuple, 0, len(in)) - for _, t := range in { - if t == nil || strings.TrimSpace(t.User) == "" || strings.TrimSpace(t.Relation) == "" || strings.TrimSpace(t.Object) == "" { - return nil, fmt.Errorf("each contextual tuple requires user, relation and object") - } - out = append(out, engine.ContextualTuple{User: t.User, Relation: t.Relation, Object: t.Object}) - } - return out, nil -} // toEngineTuples validates and converts admin-supplied tuple inputs into engine // tuples. It enforces a per-call cap and rejects empty fields. diff --git a/internal/graphql/fga_relation_check.go b/internal/graphql/fga_relation_check.go deleted file mode 100644 index b7de2224..00000000 --- a/internal/graphql/fga_relation_check.go +++ /dev/null @@ -1,49 +0,0 @@ -package graphql - -import ( - "context" - "errors" - "strings" - - "github.com/authorizerdev/authorizer/internal/authorization/engine" - "github.com/authorizerdev/authorizer/internal/graph/model" -) - -// enforceRequiredRelations gates a request on fine-grained authorization. For -// each required (relation, object) it asks the engine whether the caller -// (subject "user:") holds that relation. Semantics: -// -// - AND: every relation must be allowed. -// - Fail-closed: an engine error OR any deny => "unauthorized". -// - Empty list => authorized (preserves the prior common-case behavior where -// no fine-grained gating was requested). -// - Non-empty list with a nil engine => error (FGA not enabled but required). -// -// The subject is always derived server-side from the resolved userID, never -// from client input. -func enforceRequiredRelations(ctx context.Context, eng engine.AuthorizationEngine, userID string, required []*model.FgaRelationInput) error { - if len(required) == 0 { - return nil - } - if eng == nil { - return errFgaNotEnabled - } - if strings.TrimSpace(userID) == "" { - return errors.New("unauthorized") - } - subject := "user:" + userID - for _, r := range required { - if r == nil || strings.TrimSpace(r.Relation) == "" || strings.TrimSpace(r.Object) == "" { - return errors.New("each required relation needs relation and object") - } - allowed, err := eng.Check(ctx, subject, r.Relation, r.Object) - if err != nil { - // Fail closed. - return errors.New("unauthorized") - } - if !allowed { - return errors.New("unauthorized") - } - } - return nil -} diff --git a/internal/graphql/list_permissions.go b/internal/graphql/list_permissions.go index 1caa535f..51a9b9cc 100644 --- a/internal/graphql/list_permissions.go +++ b/internal/graphql/list_permissions.go @@ -2,144 +2,16 @@ package graphql import ( "context" - "fmt" - "sort" - "strings" - "time" - - "golang.org/x/sync/errgroup" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/metrics" - "github.com/authorizerdev/authorizer/internal/refs" + "github.com/authorizerdev/authorizer/internal/service" + "github.com/authorizerdev/authorizer/internal/utils" ) -// maxConcurrentFgaListCalls bounds the parallel ListObjects expansions issued -// by an unfiltered list_permissions call so one request cannot saturate the -// embedded engine. -const maxConcurrentFgaListCalls = 5 - -// typeRelation is one (object type, relation) pair to enumerate. -type typeRelation struct { - objType string - relation string -} - -// ListPermissions enumerates what the subject can access. With both filters -// set it answers "which s can I ?" via a single -// ListObjects call. When either filter is omitted, every matching (type, -// relation) pair of the active model is enumerated with bounded concurrency — -// an empty input returns ALL permissions the subject holds. -// -// SUBJECT TRUST GATE: same rules as CheckPermissions (token subject by -// default; explicit `user` for super-admins or self). The result set is -// capped at maxFgaListResults and `truncated` reports when the cap was hit: -// listing is an expensive enumeration surface. -// Permission: authorized user. +// ListPermissions delegates to the transport-agnostic service layer, which +// owns the subject trust gate, result caps, and fail-closed semantics. func (g *graphqlProvider) ListPermissions(ctx context.Context, params *model.ListPermissionsInput) (*model.ListPermissionsResponse, error) { - log := g.Log.With().Str("func", "ListPermissions").Logger() - if g.AuthzEngine == nil { - return nil, errFgaNotEnabled - } - if params == nil { - params = &model.ListPermissionsInput{} - } - relationFilter := strings.TrimSpace(refs.StringValue(params.Relation)) - typeFilter := strings.TrimSpace(refs.StringValue(params.ObjectType)) - subject, err := g.resolveFgaSubject(ctx, refs.StringValue(params.User)) - if err != nil { - log.Debug().Err(err).Msg("Failed to resolve subject") - return nil, err - } - - start := time.Now() - pairs, err := g.listPermissionPairs(ctx, relationFilter, typeFilter) - if err != nil { - metrics.RecordFgaOperation(metrics.FgaOpListPermissions, metrics.FgaResultError) - log.Debug().Err(err).Msg("Failed to resolve model type relations; denying") - return nil, fmt.Errorf("authorization list failed") - } - - // Enumerate each pair with bounded concurrency; results stay positionally - // aligned with pairs so aggregation order is deterministic. - results := make([][]string, len(pairs)) - eg, egCtx := errgroup.WithContext(ctx) - eg.SetLimit(maxConcurrentFgaListCalls) - for i, p := range pairs { - eg.Go(func() error { - objects, lerr := g.AuthzEngine.ListObjects(egCtx, subject, p.relation, p.objType) - if lerr != nil { - return lerr - } - results[i] = objects - return nil - }) - } - egErr := eg.Wait() - metrics.ObserveFgaCheckDuration(metrics.FgaOpListPermissions, time.Since(start).Seconds()) - if egErr != nil { - metrics.RecordFgaOperation(metrics.FgaOpListPermissions, metrics.FgaResultError) - log.Debug().Err(egErr).Msg("ListPermissions failed; denying") - return nil, fmt.Errorf("authorization list failed") - } - metrics.RecordFgaOperation(metrics.FgaOpListPermissions, metrics.FgaResultSuccess) - - // Aggregate under the global cap; `truncated` tells callers more exist. - permissions := make([]*model.Permission, 0) - objects := make([]string, 0) - seen := make(map[string]struct{}) - truncated := false - for i, objs := range results { - for _, obj := range objs { - if len(permissions) >= maxFgaListResults { - truncated = true - break - } - permissions = append(permissions, &model.Permission{Object: obj, Relation: pairs[i].relation}) - if _, ok := seen[obj]; !ok { - seen[obj] = struct{}{} - objects = append(objects, obj) - } - } - if truncated { - break - } - } - return &model.ListPermissionsResponse{ - Objects: objects, - Permissions: permissions, - Truncated: truncated, - }, nil -} - -// listPermissionPairs resolves which (type, relation) pairs to enumerate. With -// both filters present no model read is needed; otherwise the active model's -// type/relation map is filtered down, sorted for deterministic output. -func (g *graphqlProvider) listPermissionPairs(ctx context.Context, relationFilter, typeFilter string) ([]typeRelation, error) { - if relationFilter != "" && typeFilter != "" { - return []typeRelation{{objType: typeFilter, relation: relationFilter}}, nil - } - typeRels, err := g.AuthzEngine.TypeRelations(ctx) - if err != nil { - return nil, err - } - pairs := make([]typeRelation, 0) - for objType, relations := range typeRels { - if typeFilter != "" && objType != typeFilter { - continue - } - for _, relation := range relations { - if relationFilter != "" && relation != relationFilter { - continue - } - pairs = append(pairs, typeRelation{objType: objType, relation: relation}) - } - } - sort.Slice(pairs, func(i, j int) bool { - if pairs[i].objType != pairs[j].objType { - return pairs[i].objType < pairs[j].objType - } - return pairs[i].relation < pairs[j].relation - }) - return pairs, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.ListPermissions(ctx, service.MetaFromGin(gc), params) + return res, err } diff --git a/internal/graphql/logout.go b/internal/graphql/logout.go index 2c739539..83da52ae 100644 --- a/internal/graphql/logout.go +++ b/internal/graphql/logout.go @@ -3,55 +3,19 @@ package graphql import ( "context" - "github.com/authorizerdev/authorizer/internal/audit" - "github.com/authorizerdev/authorizer/internal/constants" - "github.com/authorizerdev/authorizer/internal/cookie" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/metrics" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" ) -// Logout is the method to logout a user. -// Permissions: authenticated:* +// Logout delegates to the transport-agnostic service layer. +// Permissions: authenticated user. func (g *graphqlProvider) Logout(ctx context.Context) (*model.Response, error) { - log := g.Log.With().Str("func", "Logout").Logger() - gc, err := utils.GinContextFromContext(ctx) + gc, _ := utils.GinContextFromContext(ctx) + res, side, err := g.ServiceProvider.Logout(ctx, service.MetaFromGin(gc)) if err != nil { - log.Debug().Err(err).Msg("Failed to get GinContext") return nil, err } - - tokenData, err := g.TokenProvider.GetUserIDFromSessionOrAccessToken(gc) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user id from session or access token") - return nil, err - } - - sessionKey := tokenData.UserID - if tokenData.LoginMethod != "" { - sessionKey = tokenData.LoginMethod + ":" + tokenData.UserID - } - - if err = g.MemoryStoreProvider.DeleteUserSession(sessionKey, tokenData.Nonce); err != nil { - log.Debug().Err(err).Msg("Failed to delete user session") - return nil, err - } - cookie.DeleteSession(gc, g.Config.AppCookieSecure, cookie.ParseSameSite(g.Config.AppCookieSameSite)) - metrics.RecordAuthEvent(metrics.EventLogout, metrics.StatusSuccess) - metrics.ActiveSessions.Dec() - g.AuditProvider.LogEvent(audit.Event{ - Action: constants.AuditLogoutEvent, - ActorID: tokenData.UserID, - ActorType: constants.AuditActorTypeUser, - ResourceType: constants.AuditResourceTypeSession, - ResourceID: tokenData.UserID, - IPAddress: utils.GetIP(gc.Request), - UserAgent: utils.GetUserAgent(gc.Request), - }) - - res := &model.Response{ - Message: "Logged out successfully", - } - + service.ApplyToGin(gc, side) return res, nil } diff --git a/internal/graphql/meta.go b/internal/graphql/meta.go index 6fa06d5e..94b8860b 100644 --- a/internal/graphql/meta.go +++ b/internal/graphql/meta.go @@ -3,71 +3,17 @@ package graphql import ( "context" - "github.com/authorizerdev/authorizer/internal/constants" "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/service" + "github.com/authorizerdev/authorizer/internal/utils" ) -// Meta returns the meta information about the server. +// Meta delegates to the transport-agnostic service layer. Resolver is a thin +// transport adapter — same pattern as SignUp. +// // Permissions: none func (g *graphqlProvider) Meta(ctx context.Context) (*model.Meta, error) { - clientID := g.Config.ClientID - - googleClientID := g.Config.GoogleClientID - googleClientSecret := g.Config.GoogleClientSecret - - facebookClientID := g.Config.FacebookClientID - facebookClientSecret := g.Config.FacebookClientSecret - - linkedClientID := g.Config.LinkedinClientID - linkedInClientSecret := g.Config.LinkedinClientSecret - - appleClientID := g.Config.AppleClientID - appleClientSecret := g.Config.AppleClientSecret - - githubClientID := g.Config.GithubClientID - githubClientSecret := g.Config.GithubClientSecret - - twitterClientID := g.Config.TwitterClientID - twitterClientSecret := g.Config.TwitterClientSecret - - microsoftClientID := g.Config.MicrosoftClientID - microsoftClientSecret := g.Config.MicrosoftClientSecret - - twitchClientID := g.Config.TwitchClientID - twitchClientSecret := g.Config.TwitchClientSecret - - robloxClientID := g.Config.RobloxClientID - robloxClientSecret := g.Config.RobloxClientSecret - - isBasicAuthEnabled := g.Config.EnableBasicAuthentication - isMobileBasicAuthEnabled := g.Config.EnableMobileBasicAuthentication - isMobileVerificationEnabled := g.Config.EnablePhoneVerification - isMagicLinkLoginEnabled := g.Config.EnableMagicLinkLogin - isEmailVerificationEnabled := g.Config.EnableEmailVerification - isMultiFactorAuthenticationEnabled := g.Config.EnableMFA - isStrongPasswordEnabled := g.Config.EnableStrongPassword - isSignUpEnabled := g.Config.EnableSignup - - metaInfo := model.Meta{ - Version: constants.VERSION, - ClientID: clientID, - IsGoogleLoginEnabled: googleClientID != "" && googleClientSecret != "", - IsGithubLoginEnabled: githubClientID != "" && githubClientSecret != "", - IsFacebookLoginEnabled: facebookClientID != "" && facebookClientSecret != "", - IsLinkedinLoginEnabled: linkedClientID != "" && linkedInClientSecret != "", - IsAppleLoginEnabled: appleClientID != "" && appleClientSecret != "", - IsTwitterLoginEnabled: twitterClientID != "" && twitterClientSecret != "", - IsMicrosoftLoginEnabled: microsoftClientID != "" && microsoftClientSecret != "", - IsBasicAuthenticationEnabled: isBasicAuthEnabled, - IsEmailVerificationEnabled: isEmailVerificationEnabled, - IsMagicLinkLoginEnabled: isMagicLinkLoginEnabled, - IsSignUpEnabled: isSignUpEnabled, - IsStrongPasswordEnabled: isStrongPasswordEnabled, - IsMultiFactorAuthEnabled: isMultiFactorAuthenticationEnabled, - IsMobileBasicAuthenticationEnabled: isMobileBasicAuthEnabled, - IsPhoneVerificationEnabled: isMobileVerificationEnabled, - IsTwitchLoginEnabled: twitchClientID != "" && twitchClientSecret != "", - IsRobloxLoginEnabled: robloxClientID != "" && robloxClientSecret != "", - } - return &metaInfo, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.Meta(ctx, service.MetaFromGin(gc)) + return res, err } diff --git a/internal/graphql/profile.go b/internal/graphql/profile.go index ebcb4926..d2aaecbe 100644 --- a/internal/graphql/profile.go +++ b/internal/graphql/profile.go @@ -4,29 +4,14 @@ import ( "context" "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" ) -// Profile is the method to get the profile of a user. +// Profile delegates to the transport-agnostic service layer. +// Permissions: authenticated user. func (g *graphqlProvider) Profile(ctx context.Context) (*model.User, error) { - log := g.Log.With().Str("func", "Profile").Logger() - - gc, err := utils.GinContextFromContext(ctx) - if err != nil { - log.Debug().Err(err).Msg("Failed to get GinContext") - return nil, err - } - tokenData, err := g.TokenProvider.GetUserIDFromSessionOrAccessToken(gc) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user id from session or access token") - return nil, err - } - log = log.With().Str("user_id", tokenData.UserID).Logger() - user, err := g.StorageProvider.GetUserByID(ctx, tokenData.UserID) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user by id") - return nil, err - } - - return user.AsAPIUser(), nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.Profile(ctx, service.MetaFromGin(gc)) + return res, err } diff --git a/internal/graphql/provider.go b/internal/graphql/provider.go index afa20cc1..6b4a3ea1 100644 --- a/internal/graphql/provider.go +++ b/internal/graphql/provider.go @@ -13,6 +13,7 @@ import ( "github.com/authorizerdev/authorizer/internal/events" "github.com/authorizerdev/authorizer/internal/graph/model" "github.com/authorizerdev/authorizer/internal/memory_store" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" "github.com/authorizerdev/authorizer/internal/token" @@ -39,6 +40,9 @@ type Dependencies struct { StorageProvider storage.Provider // TokenProvider is used to generate tokens TokenProvider token.Provider + // ServiceProvider hosts the transport-agnostic public-API operations. + // Resolvers for migrated ops delegate here. + ServiceProvider service.Provider // AuthzEngine is the fine-grained authorization (FGA) engine. // It is nil unless an FGA store is configured (--fga-store); // resolvers MUST fail closed (return an error) when it is nil. diff --git a/internal/graphql/revoke.go b/internal/graphql/revoke.go index 3eb11c85..c9df7d56 100644 --- a/internal/graphql/revoke.go +++ b/internal/graphql/revoke.go @@ -2,66 +2,15 @@ package graphql import ( "context" - "errors" - "strings" - "github.com/authorizerdev/authorizer/internal/constants" "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/service" + "github.com/authorizerdev/authorizer/internal/utils" ) -// Revoke is the method to revoke refresh token +// Revoke delegates to the transport-agnostic service layer. func (g *graphqlProvider) Revoke(ctx context.Context, params *model.OAuthRevokeRequest) (*model.Response, error) { - log := g.Log.With().Str("func", "Revoke").Logger() - token := strings.TrimSpace(params.RefreshToken) - if token == "" { - log.Error().Msg("Refresh token is empty") - return nil, errors.New("missing refresh token") - } - claims, err := g.TokenProvider.ParseJWTToken(token) - if err != nil { - log.Debug().Err(err).Msg("failed to parse jwt") - return nil, err - } - - userID, ok := claims["sub"].(string) - if !ok || userID == "" { - log.Debug().Msg("Invalid subject in token") - return nil, errors.New("invalid token") - } - loginMethod := claims["login_method"] - sessionToken := userID - if lm, ok := loginMethod.(string); ok && lm != "" { - sessionToken = lm + ":" + userID - } - - nonce, ok := claims["nonce"].(string) - if !ok || nonce == "" { - log.Debug().Msg("Invalid nonce in token") - return nil, errors.New("invalid token") - } - - existingToken, err := g.MemoryStoreProvider.GetUserSession(sessionToken, constants.TokenTypeRefreshToken+"_"+nonce) - if err != nil { - log.Debug().Err(err).Msg("Failed to get refresh token") - return nil, err - } - - if existingToken == "" { - log.Debug().Msg("Token not found") - return nil, errors.New("token not found") - } - - if existingToken != token { - log.Debug().Msg("Token does not match") - return nil, errors.New("token does not match") - } - - // Remove the token from the memory store - if err := g.MemoryStoreProvider.DeleteUserSession(sessionToken, nonce); err != nil { - log.Debug().Err(err).Msg("failed to delete user session") - return nil, err - } - return &model.Response{ - Message: "Token revoked", - }, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.Revoke(ctx, service.MetaFromGin(gc), params) + return res, err } diff --git a/internal/graphql/session.go b/internal/graphql/session.go index bbbdf760..37cdaf82 100644 --- a/internal/graphql/session.go +++ b/internal/graphql/session.go @@ -2,168 +2,20 @@ package graphql import ( "context" - "errors" - "fmt" - "strings" - "time" - "github.com/google/uuid" - - "github.com/authorizerdev/authorizer/internal/constants" - "github.com/authorizerdev/authorizer/internal/cookie" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/parsers" - "github.com/authorizerdev/authorizer/internal/refs" - "github.com/authorizerdev/authorizer/internal/token" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" ) -// Session is the method to get session. -// It also refreshes the session token. -// TODO allow validating with code and code verifier instead of cookie (PKCE flow) +// Session delegates to the transport-agnostic service layer; the rotated +// session cookie is applied back to the gin response from side-effects. func (g *graphqlProvider) Session(ctx context.Context, params *model.SessionQueryRequest) (*model.AuthResponse, error) { - log := g.Log.With().Str("func", "Session").Logger() - gc, err := utils.GinContextFromContext(ctx) - if err != nil { - log.Debug().Err(err).Msg("Failed to get GinContext") - return nil, err - } - - sessionToken, err := cookie.GetSession(gc) - if err != nil { - log.Debug().Err(err).Msg("Failed to get session token") - return nil, errors.New("unauthorized") - } - - // get session from cookie - claims, err := g.TokenProvider.ValidateBrowserSession(gc, sessionToken) - if err != nil { - log.Debug().Err(err).Msg("Failed to validate session token") - return nil, errors.New("unauthorized") - } - userID := claims.Subject - log = log.With().Str("user_id", userID).Logger() - user, err := g.StorageProvider.GetUserByID(ctx, userID) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user") - return nil, err - } - - // refresh token has "roles" as claim - claimRoleInterface := claims.Roles - claimRoles := []string{} - claimRoles = append(claimRoles, claimRoleInterface...) - - if params != nil && params.Roles != nil && len(params.Roles) > 0 { - for _, v := range params.Roles { - if !utils.StringSliceContains(claimRoles, v) { - log.Debug().Msg("User does not have required role") - return nil, fmt.Errorf(`unauthorized`) - } - } - } - - // Fine-grained authorization gate (AND semantics, fail-closed). - if params != nil && len(params.RequiredRelations) > 0 { - if err := enforceRequiredRelations(ctx, g.AuthzEngine, userID, params.RequiredRelations); err != nil { - log.Debug().Err(err).Msg("Required relations not satisfied") - return nil, err - } - } - - scope := []string{"openid", "email", "profile"} - if params != nil && params.Scope != nil && len(params.Scope) > 0 { - scope = params.Scope - } - - // OIDC authorize flow: if state is provided, consume the authorize state - // and prepare code/challenge data so the authorization code can be stored - // after token creation. This handles the case where the login UI auto-detects - // an existing session (e.g., prompt=login forced re-auth at /authorize but - // the session cookie is still valid for GraphQL queries). - code := "" - codeChallenge := "" - oidcNonce := "" - authorizeRedirectURI := "" - if params != nil && params.State != nil { - authorizeState, _ := g.MemoryStoreProvider.GetState(refs.StringValue(params.State)) - if authorizeState != "" { - authorizeStateSplit := strings.Split(authorizeState, "@@") - if len(authorizeStateSplit) > 1 { - code = authorizeStateSplit[0] - codeChallenge = authorizeStateSplit[1] - if len(authorizeStateSplit) > 2 { - oidcNonce = authorizeStateSplit[2] - } - if len(authorizeStateSplit) > 3 { - authorizeRedirectURI = authorizeStateSplit[3] - } - } - g.MemoryStoreProvider.RemoveState(refs.StringValue(params.State)) - } - } - - nonce := uuid.New().String() - hostname := parsers.GetHost(gc) - authToken, err := g.TokenProvider.CreateAuthToken(gc, &token.AuthTokenConfig{ - User: user, - Nonce: nonce, - OIDCNonce: oidcNonce, - Code: code, - Roles: claimRoles, - Scope: scope, - LoginMethod: claims.LoginMethod, - HostName: hostname, - }) + gc, _ := utils.GinContextFromContext(ctx) + res, side, err := g.ServiceProvider.Session(ctx, service.MetaFromGin(gc), params) if err != nil { - log.Debug().Err(err).Msg("Failed to CreateAuthToken") return nil, err } - - // Store the authorization code state so /oauth/token can find it. - // The authorizeRedirectURI is already URL-encoded from the authorize state. - if code != "" { - if err := g.MemoryStoreProvider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash+"@@"+oidcNonce+"@@"+authorizeRedirectURI); err != nil { - log.Debug().Err(err).Msg("Failed to set code state") - return nil, err - } - } - - sessionKey := userID - if claims.LoginMethod != "" { - sessionKey = claims.LoginMethod + ":" + userID - } - - expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() - if expiresIn <= 0 { - expiresIn = 1 - } - - res := &model.AuthResponse{ - Message: `Session token refreshed`, - AccessToken: &authToken.AccessToken.Token, - ExpiresIn: &expiresIn, - IDToken: &authToken.IDToken.Token, - User: user.AsAPIUser(), - } - - // Establish the new session first, then revoke the old one. Doing both - // synchronously closes the window where a stolen pre-rotation token - // remains valid alongside the rotated one; doing "new then old" avoids - // any moment where the user has no valid session token. DeleteUserSession - // is in-memory or a single Redis DEL — failure is non-fatal (log and - // continue) since the new session is already live. - cookie.SetSession(gc, authToken.FingerPrintHash, g.Config.AppCookieSecure, cookie.ParseSameSite(g.Config.AppCookieSameSite)) - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt) - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt) - - if authToken.RefreshToken != nil { - res.RefreshToken = &authToken.RefreshToken.Token - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token, authToken.RefreshToken.ExpiresAt) - } - - if err := g.MemoryStoreProvider.DeleteUserSession(sessionKey, claims.Nonce); err != nil { - log.Warn().Err(err).Str("session_key", sessionKey).Msg("failed to delete old session during rollover") - } + service.ApplyToGin(gc, side) return res, nil } diff --git a/internal/graphql/signup.go b/internal/graphql/signup.go index cec217c2..83344742 100644 --- a/internal/graphql/signup.go +++ b/internal/graphql/signup.go @@ -2,35 +2,17 @@ package graphql import ( "context" - "encoding/json" - "errors" - "fmt" - "strings" - "time" - "github.com/google/uuid" - "golang.org/x/crypto/bcrypt" - - "github.com/authorizerdev/authorizer/internal/audit" - "github.com/authorizerdev/authorizer/internal/constants" - "github.com/authorizerdev/authorizer/internal/cookie" - "github.com/authorizerdev/authorizer/internal/crypto" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/metrics" - "github.com/authorizerdev/authorizer/internal/parsers" - "github.com/authorizerdev/authorizer/internal/refs" - "github.com/authorizerdev/authorizer/internal/storage/schemas" - "github.com/authorizerdev/authorizer/internal/token" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" - "github.com/authorizerdev/authorizer/internal/validators" ) -// dummyHash is a precomputed bcrypt hash used to equalise the response time -// of the "user exists" path with the "new signup" path, preventing account -// enumeration via timing. -var dummyHash, _ = bcrypt.GenerateFromPassword([]byte("dummy-password-for-timing"), bcrypt.DefaultCost) - -// SignUp is the method to singup user +// SignUp delegates to the transport-agnostic service layer. Resolvers in this +// package are thin transport adapters: pull gin.Context out of the GraphQL +// context, build RequestMetadata, call the service, apply any cookie +// side-effects back onto gin. +// // Permission: none func (g *graphqlProvider) SignUp(ctx context.Context, params *model.SignUpRequest) (*model.AuthResponse, error) { log := g.Log.With().Str("func", "SignUp").Logger() @@ -39,363 +21,10 @@ func (g *graphqlProvider) SignUp(ctx context.Context, params *model.SignUpReques log.Debug().Err(err).Msg("Failed to get GinContext") return nil, err } - - email := strings.TrimSpace(refs.StringValue(params.Email)) - phoneNumber := strings.TrimSpace(refs.StringValue(params.PhoneNumber)) - if email == "" && phoneNumber == "" { - log.Debug().Msg("Email or phone number is required") - return nil, fmt.Errorf(`email or phone number is required`) - } - - isSignupEnabled := g.Config.EnableSignup - if !isSignupEnabled { - log.Debug().Msg("Signup is disabled") - return nil, fmt.Errorf(`signup is disabled for this instance`) - } - - isBasicAuthEnabled := g.Config.EnableBasicAuthentication - isMobileBasicAuthEnabled := g.Config.EnableMobileBasicAuthentication - if params.ConfirmPassword != params.Password { - log.Debug().Msg("Passwords do not match") - return nil, fmt.Errorf(`password and confirm password does not match`) - } - if err := validators.IsValidPassword(params.Password, !g.Config.EnableStrongPassword); err != nil { - log.Debug().Msg("Invalid password") - return nil, err - } - - log = log.With().Str("email", email).Str("phone_number", phoneNumber).Logger() - isEmailSignup := email != "" - isMobileSignup := phoneNumber != "" - if !isBasicAuthEnabled && isEmailSignup { - log.Debug().Msg("Basic authentication is disabled") - return nil, fmt.Errorf(`basic authentication is disabled for this instance`) - } - if !isMobileBasicAuthEnabled && isMobileSignup { - log.Debug().Msg("Mobile basic authentication is disabled") - return nil, fmt.Errorf(`mobile basic authentication is disabled for this instance`) - } - if isEmailSignup && !validators.IsValidEmail(email) { - log.Debug().Msg("Invalid email") - return nil, fmt.Errorf(`invalid email address`) - } - if isMobileSignup && (phoneNumber == "" || len(phoneNumber) < 10) { - log.Debug().Msg("Invalid phone number") - return nil, fmt.Errorf(`invalid phone number`) - } - // find user with email / phone number - if isEmailSignup { - existingUser, err := g.StorageProvider.GetUserByEmail(ctx, email) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user by email") - } - if existingUser != nil && (existingUser.EmailVerifiedAt != nil || existingUser.ID != "") { - log.Debug().Msg("Email is already signed up.") - bcrypt.CompareHashAndPassword(dummyHash, []byte("timing-equalization")) - return nil, fmt.Errorf("signup failed. please check your credentials or try a different method") - } - } else { - existingUser, err := g.StorageProvider.GetUserByPhoneNumber(ctx, phoneNumber) - if err != nil { - log.Debug().Err(err).Msg("Failed to get user by phone number") - } - if existingUser != nil && (existingUser.PhoneNumberVerifiedAt != nil || existingUser.ID != "") { - log.Debug().Msg("Phone number is already signed up.") - bcrypt.CompareHashAndPassword(dummyHash, []byte("timing-equalization")) - return nil, fmt.Errorf("signup failed. please check your credentials or try a different method") - } - } - - inputRoles := params.Roles - if len(inputRoles) > 0 { - // check if roles exists - roles := g.Config.Roles - if !validators.IsValidRoles(inputRoles, roles) { - log.Debug().Err(err).Strs("roles", params.Roles).Msg("Invalid roles") - return nil, fmt.Errorf(`invalid roles`) - } - } else { - inputRoles = g.Config.DefaultRoles - } - user := &schemas.User{} - user.Roles = strings.Join(inputRoles, ",") - password, _ := crypto.EncryptPassword(params.Password) - user.Password = &password - if email != "" { - user.SignupMethods = constants.AuthRecipeMethodBasicAuth - user.Email = &email - } - if params.GivenName != nil { - user.GivenName = params.GivenName - } - - if params.FamilyName != nil { - user.FamilyName = params.FamilyName - } - - if params.MiddleName != nil { - user.MiddleName = params.MiddleName - } - - if params.Nickname != nil { - user.Nickname = params.Nickname - } - - if params.Gender != nil { - user.Gender = params.Gender - } - - if params.Birthdate != nil { - user.Birthdate = params.Birthdate - } - - if phoneNumber != "" { - user.SignupMethods = constants.AuthRecipeMethodMobileBasicAuth - user.PhoneNumber = refs.NewStringRef(phoneNumber) - } - - if params.Picture != nil { - user.Picture = params.Picture - } - - if params.IsMultiFactorAuthEnabled != nil { - user.IsMultiFactorAuthEnabled = params.IsMultiFactorAuthEnabled - } - - isMFAEnforced := g.Config.EnforceMFA - if isMFAEnforced { - user.IsMultiFactorAuthEnabled = refs.NewBoolRef(true) - } - - if params.AppData != nil { - appDataString := "" - appDataBytes, err := json.Marshal(params.AppData) - if err != nil { - log.Debug().Msg("failed to marshall source app_data") - return nil, errors.New("malformed app_data") - } - appDataString = string(appDataBytes) - user.AppData = &appDataString - } - isEmailServiceEnabled := g.Config.IsEmailServiceEnabled - isEmailVerificationEnabled := g.Config.EnableEmailVerification && isEmailServiceEnabled - if !isEmailVerificationEnabled && isEmailSignup { - now := time.Now().Unix() - user.EmailVerifiedAt = &now - } - isSMSServiceEnabled := g.Config.IsSMSServiceEnabled - isPhoneVerificationEnabled := g.Config.EnablePhoneVerification && isSMSServiceEnabled - if !isPhoneVerificationEnabled && isMobileSignup { - now := time.Now().Unix() - user.PhoneNumberVerifiedAt = &now - } - user, err = g.StorageProvider.AddUser(ctx, user) - if err != nil { - log.Debug().Err(err).Msg("failed to add user") - return nil, err - } - roles := strings.Split(user.Roles, ",") - userToReturn := user.AsAPIUser() - hostname := parsers.GetHost(gc) - if isEmailVerificationEnabled && isEmailSignup { - // insert verification request - _, nonceHash, err := utils.GenerateNonce() - if err != nil { - log.Debug().Err(err).Msg("Failed to generate nonce") - return nil, err - } - verificationType := constants.VerificationTypeBasicAuthSignup - redirectURL := parsers.GetAppURL(gc) - if params.RedirectURI != nil { - redirectURL = *params.RedirectURI - if !validators.IsValidRedirectURI(redirectURL, g.Config.AllowedOrigins, hostname) { - log.Debug().Msg("Invalid redirect URI") - return nil, fmt.Errorf("invalid redirect URI") - } - } - verificationToken, err := g.TokenProvider.CreateVerificationToken(&token.AuthTokenConfig{ - Nonce: nonceHash, - HostName: hostname, - User: user, - LoginMethod: constants.AuthRecipeMethodBasicAuth, - }, redirectURL, verificationType) - if err != nil { - log.Debug().Err(err).Msg("Failed to create verification token") - return nil, err - } - _, err = g.StorageProvider.AddVerificationRequest(ctx, &schemas.VerificationRequest{ - Token: verificationToken, - Identifier: verificationType, - ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), - Email: email, - Nonce: nonceHash, - RedirectURI: redirectURL, - }) - if err != nil { - log.Debug().Err(err).Msg("Failed to add verification request") - return nil, err - } - // exec it as go routine so that we can reduce the api latency - go func() { - // exec it as go routine so that we can reduce the api latency - g.EmailProvider.SendEmail([]string{email}, constants.VerificationTypeBasicAuthSignup, map[string]interface{}{ - "user": user.ToMap(), - "organization": utils.GetOrganization(g.Config), - "verification_url": utils.GetEmailVerificationURL(verificationToken, hostname, redirectURL), - }) - g.EventsProvider.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user) - }() - - return &model.AuthResponse{ - Message: `Verification email has been sent. Please check your inbox`, - }, nil - } else if isPhoneVerificationEnabled && isMobileSignup { - duration, _ := time.ParseDuration("10m") - smsCode, err := utils.GenerateOTP() - if err != nil { - log.Debug().Err(err).Msg("Failed to generate OTP") - return nil, err - } - smsBody := strings.Builder{} - smsBody.WriteString("Your verification code is: ") - smsBody.WriteString(smsCode) - expiresAt := time.Now().Add(duration).Unix() - // Store the HMAC digest of the OTP; smsCode (plaintext) is sent - // over SMS by the existing smsBody above. - _, err = g.StorageProvider.UpsertOTP(ctx, &schemas.OTP{ - PhoneNumber: phoneNumber, - Otp: crypto.HashOTP(smsCode, g.Config.JWTSecret), - ExpiresAt: expiresAt, - }) - if err != nil { - log.Debug().Err(err).Msg("error while upserting OTP") - return nil, err - } - mfaSession := uuid.NewString() - err = g.MemoryStoreProvider.SetMfaSession(user.ID, mfaSession, expiresAt) - if err != nil { - log.Debug().Err(err).Msg("Failed to add mfasession") - return nil, err - } - cookie.SetMfaSession(gc, mfaSession, g.Config.AppCookieSecure) - go func() { - g.SMSProvider.SendSMS(phoneNumber, smsBody.String()) - g.EventsProvider.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, user) - }() - return &model.AuthResponse{ - Message: "Please check the OTP in your inbox", - ShouldShowMobileOtpScreen: refs.NewBoolRef(true), - }, nil - } - scope := []string{"openid", "email", "profile"} - if params.Scope != nil && len(params.Scope) > 0 { - scope = params.Scope - } - - code := "" - codeChallenge := "" - nonce := "" - oidcNonce := "" - authorizeRedirectURI := "" - if params.State != nil { - // Get state from store - authorizeState, _ := g.MemoryStoreProvider.GetState(refs.StringValue(params.State)) - if authorizeState != "" { - authorizeStateSplit := strings.Split(authorizeState, "@@") - if len(authorizeStateSplit) > 1 { - code = authorizeStateSplit[0] - codeChallenge = authorizeStateSplit[1] - if len(authorizeStateSplit) > 2 { - oidcNonce = authorizeStateSplit[2] - } - if len(authorizeStateSplit) > 3 { - authorizeRedirectURI = authorizeStateSplit[3] - } - } else { - nonce = authorizeState - } - g.MemoryStoreProvider.RemoveState(refs.StringValue(params.State)) - } - } - - if nonce == "" { - nonce = uuid.New().String() - } - authToken, err := g.TokenProvider.CreateAuthToken(gc, &token.AuthTokenConfig{ - User: user, - Roles: roles, - Scope: scope, - Nonce: nonce, - OIDCNonce: oidcNonce, - Code: code, - LoginMethod: constants.AuthRecipeMethodBasicAuth, - HostName: hostname, - }) + res, side, err := g.ServiceProvider.SignUp(ctx, service.MetaFromGin(gc), params) if err != nil { - log.Debug().Err(err).Msg("Failed to create auth token") return nil, err } - - // Code challenge could be optional if PKCE flow is not used - if code != "" { - if err := g.MemoryStoreProvider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash+"@@"+oidcNonce+"@@"+authorizeRedirectURI); err != nil { - log.Debug().Err(err).Msg("SetState failed") - return nil, err - } - } - - expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() - if expiresIn <= 0 { - expiresIn = 1 - } - - res := &model.AuthResponse{ - Message: `Signed up successfully.`, - AccessToken: &authToken.AccessToken.Token, - ExpiresIn: &expiresIn, - User: userToReturn, - } - - sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID - cookie.SetSession(gc, authToken.FingerPrintHash, g.Config.AppCookieSecure, cookie.ParseSameSite(g.Config.AppCookieSameSite)) - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt) - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt) - - if authToken.RefreshToken != nil { - res.RefreshToken = &authToken.RefreshToken.Token - g.MemoryStoreProvider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token, authToken.RefreshToken.ExpiresAt) - } - - go func() { - g.EventsProvider.RegisterEvent(ctx, constants.UserCreatedWebhookEvent, constants.AuthRecipeMethodBasicAuth, user) - if isEmailSignup { - g.EventsProvider.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, constants.AuthRecipeMethodBasicAuth, user) - g.EventsProvider.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodBasicAuth, user) - } else { - g.EventsProvider.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, user) - g.EventsProvider.RegisterEvent(ctx, constants.UserLoginWebhookEvent, constants.AuthRecipeMethodMobileBasicAuth, user) - } - - if err := g.StorageProvider.AddSession(ctx, &schemas.Session{ - UserID: user.ID, - UserAgent: utils.GetUserAgent(gc.Request), - IP: utils.GetIP(gc.Request), - }); err != nil { - log.Debug().Err(err).Msg("Failed to add session") - } - }() - metrics.RecordAuthEvent(metrics.EventSignup, metrics.StatusSuccess) - metrics.ActiveSessions.Inc() - g.AuditProvider.LogEvent(audit.Event{ - Action: constants.AuditSignupEvent, - ActorID: user.ID, - ActorType: constants.AuditActorTypeUser, - ActorEmail: refs.StringValue(user.Email), - ResourceType: constants.AuditResourceTypeUser, - ResourceID: user.ID, - IPAddress: utils.GetIP(gc.Request), - UserAgent: utils.GetUserAgent(gc.Request), - }) - + service.ApplyToGin(gc, side) return res, nil } diff --git a/internal/graphql/validate_jwt_token.go b/internal/graphql/validate_jwt_token.go index c2f7d06a..8a815742 100644 --- a/internal/graphql/validate_jwt_token.go +++ b/internal/graphql/validate_jwt_token.go @@ -2,138 +2,15 @@ package graphql import ( "context" - "errors" - "fmt" - "github.com/golang-jwt/jwt/v4" - - "github.com/authorizerdev/authorizer/internal/constants" "github.com/authorizerdev/authorizer/internal/graph/model" - "github.com/authorizerdev/authorizer/internal/parsers" - "github.com/authorizerdev/authorizer/internal/storage/schemas" - "github.com/authorizerdev/authorizer/internal/token" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" ) -// ValidateJwtToken is used to validate a jwt token without its rotation -// this can be used at API level (backend) -// it can validate: -// access_token -// id_token -// refresh_token -// Permission: none +// ValidateJWTToken delegates to the transport-agnostic service layer. func (g *graphqlProvider) ValidateJWTToken(ctx context.Context, params *model.ValidateJWTTokenRequest) (*model.ValidateJWTTokenResponse, error) { - log := g.Log.With().Str("func", "ValidateJWTToken").Logger() - gc, err := utils.GinContextFromContext(ctx) - if err != nil { - log.Debug().Err(err).Msg("Failed to get GinContext") - return nil, err - } - - tokenType := params.TokenType - if tokenType != constants.TokenTypeAccessToken && tokenType != constants.TokenTypeRefreshToken && tokenType != constants.TokenTypeIdentityToken { - log.Debug().Str("token_type", tokenType).Msg("Invalid token type") - return nil, errors.New("invalid token type") - } - - var claimRoles []string - var claims jwt.MapClaims - userID := "" - nonce := "" - - claims, err = g.TokenProvider.ParseJWTToken(params.Token) - if err != nil { - log.Debug().Err(err).Msg("Failed to parse jwt token") - return nil, err - } - sub, ok := claims["sub"].(string) - if !ok || sub == "" { - log.Debug().Msg("Invalid subject in token") - return nil, errors.New("invalid token") - } - userID = sub - - // access_token and refresh_token should be validated from session store as well - if tokenType == constants.TokenTypeAccessToken || tokenType == constants.TokenTypeRefreshToken { - nonceVal, ok := claims["nonce"].(string) - if !ok || nonceVal == "" { - log.Debug().Msg("Invalid nonce in token") - return nil, errors.New("invalid token") - } - nonce = nonceVal - loginMethod := claims["login_method"] - sessionKey := userID - if lm, ok := loginMethod.(string); ok && lm != "" { - sessionKey = lm + ":" + userID - } - token, err := g.MemoryStoreProvider.GetUserSession(sessionKey, tokenType+"_"+nonceVal) - if err != nil || token == "" { - log.Debug().Err(err).Msg("Failed to get token from session store") - return nil, errors.New("invalid token") - } - } - - hostname := parsers.GetHost(gc) - - // we cannot validate nonce in case of id_token as that token is not persisted in session store - if nonce != "" { - if ok, err := g.TokenProvider.ValidateJWTClaims(claims, &token.AuthTokenConfig{ - HostName: hostname, - Nonce: nonce, - User: &schemas.User{ - ID: userID, - }, - }); !ok || err != nil { - log.Debug().Err(err).Msg("Failed to validate jwt claims") - return nil, errors.New("invalid claims") - } - } else { - if ok, err := g.TokenProvider.ValidateJWTTokenWithoutNonce(claims, &token.AuthTokenConfig{ - HostName: hostname, - User: &schemas.User{ - ID: userID, - }, - }); !ok || err != nil { - log.Debug().Err(err).Msg("Failed to validate jwt claims") - return nil, errors.New("invalid claims") - } - } - - // Read roles from the configured claim key (used for id_token), falling - // back to the hardcoded "roles" claim that CreateAccessToken emits. This - // avoids a missing-roles principal when JWTRoleClaim is the default - // "role" (singular) but the token is an access_token (plural "roles"). - claimRolesInterface := claims[g.Config.JWTRoleClaim] - roleSlice := utils.ConvertInterfaceToSlice(claimRolesInterface) - if len(roleSlice) == 0 { - roleSlice = utils.ConvertInterfaceToSlice(claims["roles"]) - } - for _, v := range roleSlice { - roleStr, ok := v.(string) - if !ok || roleStr == "" { - log.Debug().Msg("Invalid role claim value") - return nil, errors.New("invalid claims") - } - claimRoles = append(claimRoles, roleStr) - } - - if len(params.Roles) > 0 { - for _, v := range params.Roles { - if !utils.StringSliceContains(claimRoles, v) { - log.Debug().Str("role", v).Msg("Role not found in claims") - return nil, fmt.Errorf(`unauthorized`) - } - } - } - // Fine-grained authorization gate (AND semantics, fail-closed). - if len(params.RequiredRelations) > 0 { - if err := enforceRequiredRelations(ctx, g.AuthzEngine, userID, params.RequiredRelations); err != nil { - log.Debug().Err(err).Msg("Required relations not satisfied") - return nil, err - } - } - return &model.ValidateJWTTokenResponse{ - IsValid: true, - Claims: claims, - }, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.ValidateJwtToken(ctx, service.MetaFromGin(gc), params) + return res, err } diff --git a/internal/graphql/validate_session.go b/internal/graphql/validate_session.go index d8c23f86..06fcbd4e 100644 --- a/internal/graphql/validate_session.go +++ b/internal/graphql/validate_session.go @@ -2,73 +2,15 @@ package graphql import ( "context" - "errors" - "fmt" - "github.com/authorizerdev/authorizer/internal/cookie" "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/utils" ) -// ValidateSession is used to validate a cookie session without its rotation -// Permission: authorized:user +// ValidateSession delegates to the transport-agnostic service layer. func (g *graphqlProvider) ValidateSession(ctx context.Context, params *model.ValidateSessionRequest) (*model.ValidateSessionResponse, error) { - log := g.Log.With().Str("func", "ValidateSession").Logger() - gc, err := utils.GinContextFromContext(ctx) - if err != nil { - log.Debug().Err(err).Msg("Failed to get GinContext") - return nil, err - } - sessionToken := "" - if params != nil && params.Cookie != "" { - sessionToken = params.Cookie - } else { - sessionToken, err = cookie.GetSession(gc) - if err != nil { - log.Debug().Err(err).Msg("Failed to get session token") - return nil, errors.New("unauthorized") - } - } - if sessionToken == "" { - sessionToken, err = cookie.GetSession(gc) - if err != nil { - log.Debug().Err(err).Msg("Failed to get session token") - return nil, errors.New("unauthorized") - } - } - claims, err := g.TokenProvider.ValidateBrowserSession(gc, sessionToken) - if err != nil { - log.Debug().Err(err).Msg("Failed to validate session") - return nil, errors.New("unauthorized") - } - userID := claims.Subject - log.Debug().Str("userID", userID).Msg("Validated session") - user, err := g.StorageProvider.GetUserByID(ctx, userID) - if err != nil { - log.Debug().Err(err).Msg("failed GetUserByID") - return nil, err - } - // refresh token has "roles" as claim - claimRoleInterface := claims.Roles - claimRoles := []string{} - claimRoles = append(claimRoles, claimRoleInterface...) - if params != nil && params.Roles != nil && len(params.Roles) > 0 { - for _, v := range params.Roles { - if !utils.StringSliceContains(claimRoles, v) { - log.Debug().Str("role", v).Msg("Role not found in claims") - return nil, fmt.Errorf(`unauthorized`) - } - } - } - // Fine-grained authorization gate (AND semantics, fail-closed). - if params != nil && len(params.RequiredRelations) > 0 { - if err := enforceRequiredRelations(ctx, g.AuthzEngine, userID, params.RequiredRelations); err != nil { - log.Debug().Err(err).Msg("Required relations not satisfied") - return nil, err - } - } - return &model.ValidateSessionResponse{ - IsValid: true, - User: user.AsAPIUser(), - }, nil + gc, _ := utils.GinContextFromContext(ctx) + res, _, err := g.ServiceProvider.ValidateSession(ctx, service.MetaFromGin(gc), params) + return res, err } diff --git a/internal/grpcsrv/handlers/authorizer.go b/internal/grpcsrv/handlers/authorizer.go new file mode 100644 index 00000000..89a5c8d2 --- /dev/null +++ b/internal/grpcsrv/handlers/authorizer.go @@ -0,0 +1,235 @@ +// Package handlers contains the AuthorizerHandler, the single gRPC service +// handler for Authorizer's public API. Methods that have already been +// migrated into internal/service (currently just Meta) delegate there; the +// rest embed the proto-generated UnimplementedAuthorizerServer so they +// return codes.Unimplemented until their underlying service method lands. +// +// As each follow-up PR migrates one GraphQL op into internal/service, the +// corresponding stub here is replaced with a real delegation following the +// Meta pattern. Tests in internal/integration_tests/grpc_surface_test.go +// guard the Unimplemented contract until each migration ships. +package handlers + +import ( + "context" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" + "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/grpcsrv/transport" + "github.com/authorizerdev/authorizer/internal/refs" + "github.com/authorizerdev/authorizer/internal/service" +) + +// AuthorizerHandler implements authorizer.v1.AuthorizerService. The single +// struct satisfies the entire service interface; methods become real one at +// a time. The Go type name stays "AuthorizerHandler" (not "...ServiceHandler") +// because in Go we don't repeat the "Service" suffix at the call site. +type AuthorizerHandler struct { + authorizerv1.UnimplementedAuthorizerServiceServer + Service service.Provider +} + +// Signup delegates to service.SignUp, applies session/MFA cookie side-effects +// to the outgoing stream (grpc-gateway lifts them to Set-Cookie for REST +// callers), and projects the AuthResponse. Proto3 scalars carry no presence, +// so optional string inputs collapse empty -> nil via optionalString to match +// the GraphQL "field omitted" semantics. Signup is intentionally NOT MCP- +// exposed (it returns credentials). +func (h *AuthorizerHandler) Signup(ctx context.Context, req *authorizerv1.SignupRequest) (*authorizerv1.SignupResponse, error) { + res, side, err := h.Service.SignUp(ctx, transport.MetaFromGRPC(ctx), &model.SignUpRequest{ + Email: optionalString(req.Email), + PhoneNumber: optionalString(req.PhoneNumber), + Password: req.Password, + ConfirmPassword: req.ConfirmPassword, + GivenName: optionalString(req.GivenName), + FamilyName: optionalString(req.FamilyName), + MiddleName: optionalString(req.MiddleName), + Nickname: optionalString(req.Nickname), + Gender: optionalString(req.Gender), + Birthdate: optionalString(req.Birthdate), + Picture: optionalString(req.Picture), + Roles: req.Roles, + Scope: req.Scope, + RedirectURI: optionalString(req.RedirectUri), + IsMultiFactorAuthEnabled: &req.IsMultiFactorAuthEnabled, + State: optionalString(req.State), + AppData: appDataToMap(req.AppData), + }) + if err != nil { + return nil, err + } + _ = transport.ApplyToGRPC(ctx, side) + return &authorizerv1.SignupResponse{Auth: projectAuthResponse(res)}, nil +} + +// Revoke delegates to service.Revoke and projects the result. +func (h *AuthorizerHandler) Revoke(ctx context.Context, req *authorizerv1.RevokeRequest) (*authorizerv1.RevokeResponse, error) { + res, _, err := h.Service.Revoke(ctx, transport.MetaFromGRPC(ctx), &model.OAuthRevokeRequest{RefreshToken: req.RefreshToken}) + if err != nil { + return nil, err + } + return &authorizerv1.RevokeResponse{Message: res.Message}, nil +} + +// ValidateJwtToken delegates to service.ValidateJwtToken. The JWT claims +// map (free-form) is projected to AppData (which wraps Struct) to preserve +// the existing GraphQL semantics. +func (h *AuthorizerHandler) ValidateJwtToken(ctx context.Context, req *authorizerv1.ValidateJwtTokenRequest) (*authorizerv1.ValidateJwtTokenResponse, error) { + res, _, err := h.Service.ValidateJwtToken(ctx, transport.MetaFromGRPC(ctx), &model.ValidateJWTTokenRequest{ + TokenType: req.TokenType, + Token: req.Token, + Roles: req.Roles, + RequiredRelations: protoToModelRequiredRelations(req.RequiredRelations), + }) + if err != nil { + return nil, err + } + return &authorizerv1.ValidateJwtTokenResponse{ + IsValid: res.IsValid, + Claims: mapToAppData(res.Claims), + }, nil +} + +// ValidateSession delegates to service.ValidateSession. +func (h *AuthorizerHandler) ValidateSession(ctx context.Context, req *authorizerv1.ValidateSessionRequest) (*authorizerv1.ValidateSessionResponse, error) { + res, _, err := h.Service.ValidateSession(ctx, transport.MetaFromGRPC(ctx), &model.ValidateSessionRequest{ + Cookie: req.Cookie, + Roles: req.Roles, + RequiredRelations: protoToModelRequiredRelations(req.RequiredRelations), + }) + if err != nil { + return nil, err + } + return &authorizerv1.ValidateSessionResponse{ + IsValid: res.IsValid, + User: projectUser(res.User), + }, nil +} + +// Session delegates to service.Session, applies the rotated session cookie +// to the outgoing stream, and projects the AuthResponse. SessionResponse +// carries credentials and is intentionally NOT MCP-exposed (audit C1). +func (h *AuthorizerHandler) Session(ctx context.Context, req *authorizerv1.SessionRequest) (*authorizerv1.SessionResponse, error) { + res, side, err := h.Service.Session(ctx, transport.MetaFromGRPC(ctx), &model.SessionQueryRequest{ + Roles: req.Roles, + Scope: req.Scope, + State: refs.NewStringRef(req.State), + RequiredRelations: protoToModelRequiredRelations(req.RequiredRelations), + }) + if err != nil { + return nil, err + } + _ = transport.ApplyToGRPC(ctx, side) + return &authorizerv1.SessionResponse{Auth: projectAuthResponse(res)}, nil +} + +// Profile delegates to service.Profile and projects the result into the +// proto ProfileResponse. Requires session/bearer auth (handled inside the +// service via TokenProvider.GetUserIDFromSessionOrAccessToken). +func (h *AuthorizerHandler) Profile(ctx context.Context, _ *authorizerv1.ProfileRequest) (*authorizerv1.ProfileResponse, error) { + u, _, err := h.Service.Profile(ctx, transport.MetaFromGRPC(ctx)) + if err != nil { + return nil, err + } + return &authorizerv1.ProfileResponse{User: projectUser(u)}, nil +} + +// CheckPermissions delegates to service.CheckPermissions and projects the +// per-check results. The subject trust gate and fail-closed semantics live +// in the service layer. +func (h *AuthorizerHandler) CheckPermissions(ctx context.Context, req *authorizerv1.CheckPermissionsRequest) (*authorizerv1.CheckPermissionsResponse, error) { + params := &model.CheckPermissionsInput{ + Checks: protoToModelPermissionChecks(req.Checks), + } + if req.User != "" { + params.User = refs.NewStringRef(req.User) + } + res, _, err := h.Service.CheckPermissions(ctx, transport.MetaFromGRPC(ctx), params) + if err != nil { + return nil, err + } + out := make([]*authorizerv1.PermissionCheckResult, 0, len(res.Results)) + for _, r := range res.Results { + out = append(out, &authorizerv1.PermissionCheckResult{ + Relation: r.Relation, + Object: r.Object, + Allowed: r.Allowed, + }) + } + return &authorizerv1.CheckPermissionsResponse{Results: out}, nil +} + +// ListPermissions delegates to service.ListPermissions and projects the +// (object, relation) pairs plus the distinct object list. +func (h *AuthorizerHandler) ListPermissions(ctx context.Context, req *authorizerv1.ListPermissionsRequest) (*authorizerv1.ListPermissionsResponse, error) { + params := &model.ListPermissionsInput{} + if req.Relation != "" { + params.Relation = refs.NewStringRef(req.Relation) + } + if req.ObjectType != "" { + params.ObjectType = refs.NewStringRef(req.ObjectType) + } + if req.User != "" { + params.User = refs.NewStringRef(req.User) + } + res, _, err := h.Service.ListPermissions(ctx, transport.MetaFromGRPC(ctx), params) + if err != nil { + return nil, err + } + perms := make([]*authorizerv1.Permission, 0, len(res.Permissions)) + for _, p := range res.Permissions { + perms = append(perms, &authorizerv1.Permission{Object: p.Object, Relation: p.Relation}) + } + return &authorizerv1.ListPermissionsResponse{ + Objects: res.Objects, + Permissions: perms, + Truncated: res.Truncated, + }, nil +} + +// Logout delegates to service.Logout, applies any cookie side-effects to +// the outgoing gRPC stream (grpc-gateway lifts them to Set-Cookie when +// the call came in via REST), then returns the typed response. +func (h *AuthorizerHandler) Logout(ctx context.Context, _ *authorizerv1.LogoutRequest) (*authorizerv1.LogoutResponse, error) { + res, side, err := h.Service.Logout(ctx, transport.MetaFromGRPC(ctx)) + if err != nil { + return nil, err + } + // Best-effort: cookie application is out-of-band; a SendHeader failure + // degrades to "user has to re-auth" rather than failing the request. + _ = transport.ApplyToGRPC(ctx, side) + return &authorizerv1.LogoutResponse{Message: res.Message}, nil +} + +// Meta delegates to service.Meta and projects the GraphQL Meta model into +// the proto MetaResponse. +func (h *AuthorizerHandler) Meta(ctx context.Context, _ *authorizerv1.MetaRequest) (*authorizerv1.MetaResponse, error) { + m, _, err := h.Service.Meta(ctx, transport.MetaFromGRPC(ctx)) + if err != nil { + return nil, err + } + return &authorizerv1.MetaResponse{ + Meta: &authorizerv1.Meta{ + Version: m.Version, + ClientId: m.ClientID, + IsGoogleLoginEnabled: m.IsGoogleLoginEnabled, + IsFacebookLoginEnabled: m.IsFacebookLoginEnabled, + IsGithubLoginEnabled: m.IsGithubLoginEnabled, + IsLinkedinLoginEnabled: m.IsLinkedinLoginEnabled, + IsAppleLoginEnabled: m.IsAppleLoginEnabled, + IsDiscordLoginEnabled: m.IsDiscordLoginEnabled, + IsTwitterLoginEnabled: m.IsTwitterLoginEnabled, + IsMicrosoftLoginEnabled: m.IsMicrosoftLoginEnabled, + IsTwitchLoginEnabled: m.IsTwitchLoginEnabled, + IsRobloxLoginEnabled: m.IsRobloxLoginEnabled, + IsEmailVerificationEnabled: m.IsEmailVerificationEnabled, + IsBasicAuthenticationEnabled: m.IsBasicAuthenticationEnabled, + IsMagicLinkLoginEnabled: m.IsMagicLinkLoginEnabled, + IsSignUpEnabled: m.IsSignUpEnabled, + IsStrongPasswordEnabled: m.IsStrongPasswordEnabled, + IsMultiFactorAuthEnabled: m.IsMultiFactorAuthEnabled, + IsMobileBasicAuthenticationEnabled: m.IsMobileBasicAuthenticationEnabled, + IsPhoneVerificationEnabled: m.IsPhoneVerificationEnabled, + }, + }, nil +} diff --git a/internal/grpcsrv/handlers/project.go b/internal/grpcsrv/handlers/project.go new file mode 100644 index 00000000..af738466 --- /dev/null +++ b/internal/grpcsrv/handlers/project.go @@ -0,0 +1,194 @@ +// Package handlers — projection helpers in this file convert the +// GraphQL/storage model types returned by service.* into the proto wire types. +// Centralised here so each handler can stay focused on its delegation pattern. +package handlers + +import ( + "encoding/json" + + "google.golang.org/protobuf/types/known/structpb" + + "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/refs" + + commonv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/common/v1" + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// projectUser converts the GraphQL User model into the proto User message. +// Optional fields (nil pointers / nil maps) collapse to zero values; the +// gateway's UseProtoNames + EmitUnpopulated configuration makes them +// visible to REST clients regardless. +func projectUser(u *model.User) *authorizerv1.User { + if u == nil { + return nil + } + out := &authorizerv1.User{ + Id: u.ID, + Email: refs.StringValue(u.Email), + EmailVerified: u.EmailVerified, + SignupMethods: u.SignupMethods, + GivenName: refs.StringValue(u.GivenName), + FamilyName: refs.StringValue(u.FamilyName), + MiddleName: refs.StringValue(u.MiddleName), + Nickname: refs.StringValue(u.Nickname), + PreferredUsername: refs.StringValue(u.PreferredUsername), + Gender: refs.StringValue(u.Gender), + Birthdate: refs.StringValue(u.Birthdate), + PhoneNumber: refs.StringValue(u.PhoneNumber), + PhoneNumberVerified: u.PhoneNumberVerified, + Picture: refs.StringValue(u.Picture), + Roles: u.Roles, + CreatedAt: refs.Int64Value(u.CreatedAt), + UpdatedAt: refs.Int64Value(u.UpdatedAt), + RevokedTimestamp: refs.Int64Value(u.RevokedTimestamp), + IsMultiFactorAuthEnabled: refs.BoolValue(u.IsMultiFactorAuthEnabled), + } + if u.AppData != nil { + out.AppData = mapToAppData(u.AppData) + } + return out +} + +// projectAuthResponse converts the GraphQL AuthResponse model into the +// proto AuthResponse. Used by the Session handler; the credential fields +// (tokens, authenticator secret/recovery codes) are passed through to gRPC +// + REST callers but the proto annotation on Session intentionally keeps +// it OFF the MCP surface (security audit C1). +func projectAuthResponse(a *model.AuthResponse) *authorizerv1.AuthResponse { + if a == nil { + return nil + } + return &authorizerv1.AuthResponse{ + Message: a.Message, + ShouldShowEmailOtpScreen: refs.BoolValue(a.ShouldShowEmailOtpScreen), + ShouldShowMobileOtpScreen: refs.BoolValue(a.ShouldShowMobileOtpScreen), + ShouldShowTotpScreen: refs.BoolValue(a.ShouldShowTotpScreen), + AccessToken: refs.StringValue(a.AccessToken), + IdToken: refs.StringValue(a.IDToken), + RefreshToken: refs.StringValue(a.RefreshToken), + ExpiresIn: refs.Int64Value(a.ExpiresIn), + User: projectUser(a.User), + AuthenticatorScannerImage: refs.StringValue(a.AuthenticatorScannerImage), + AuthenticatorSecret: refs.StringValue(a.AuthenticatorSecret), + AuthenticatorRecoveryCodes: derefStringSlice(a.AuthenticatorRecoveryCodes), + } +} + +// mapToAppData converts a free-form map (GraphQL's `Map` for user app_data, or +// a JWT claims bag) into the proto AppData wrapper around +// google.protobuf.Struct. JSON is the lingua franca — it matches how AppData is +// persisted today and tolerates anything the JWT library or storage layer +// produces. +// +// A conversion failure (unmarshalable value, or a value Struct cannot +// represent) collapses to nil rather than failing the whole response: app_data +// is advisory metadata and must never take down an otherwise-valid User or +// claims payload. The inputs are produced by our own storage/JWT layers, so a +// failure here indicates corrupt data worth surfacing out-of-band, not a +// client error. +func mapToAppData(m map[string]any) *commonv1.AppData { + if len(m) == 0 { + return nil + } + // Round-trip via JSON so anything Struct can't represent natively + // (e.g. nested numbers > int64) gets surfaced consistently. + b, err := json.Marshal(m) + if err != nil { + return nil + } + var generic map[string]any + if err := json.Unmarshal(b, &generic); err != nil { + return nil + } + s, err := structpb.NewStruct(generic) + if err != nil { + return nil + } + return &commonv1.AppData{Value: s} +} + +// appDataToMap is the inverse of mapToAppData: it unwraps the proto AppData +// (google.protobuf.Struct) back into a free-form map for the model layer. +// Returns nil for an absent/empty AppData so optional semantics are preserved. +func appDataToMap(in *commonv1.AppData) map[string]any { + if in == nil || in.GetValue() == nil { + return nil + } + return in.GetValue().AsMap() +} + +// optionalString maps a proto3 scalar string onto the model layer's nullable +// *string: an empty wire value means "unset" and collapses to nil, matching +// how GraphQL omits an absent optional input field. +func optionalString(s string) *string { + if s == "" { + return nil + } + return &s +} + +// derefStringSlice converts []*string (GraphQL's nullable string list shape) +// into []string, dropping nil entries. +func derefStringSlice(in []*string) []string { + if len(in) == 0 { + return nil + } + out := make([]string, 0, len(in)) + for _, s := range in { + if s != nil { + out = append(out, *s) + } + } + return out +} + +// protoToModelRequiredRelations converts the proto FgaRelationInput repeated +// field into the GraphQL model.FgaRelationInput slice used by service methods. +func protoToModelRequiredRelations(in []*authorizerv1.FgaRelationInput) []*model.FgaRelationInput { + if len(in) == 0 { + return nil + } + out := make([]*model.FgaRelationInput, 0, len(in)) + for _, r := range in { + if r == nil { + continue + } + out = append(out, &model.FgaRelationInput{ + Relation: r.Relation, + Object: r.Object, + }) + } + return out +} + +// protoToModelPermissionChecks converts the proto PermissionCheckInput +// repeated field (including request-scoped contextual tuples) into the +// GraphQL model slice used by service.CheckPermissions. +func protoToModelPermissionChecks(in []*authorizerv1.PermissionCheckInput) []*model.PermissionCheckInput { + if len(in) == 0 { + return nil + } + out := make([]*model.PermissionCheckInput, 0, len(in)) + for _, c := range in { + if c == nil { + continue + } + check := &model.PermissionCheckInput{ + Relation: c.Relation, + Object: c.Object, + } + for _, t := range c.ContextualTuples { + if t == nil { + continue + } + check.ContextualTuples = append(check.ContextualTuples, &model.FgaTupleInput{ + User: t.User, + Relation: t.Relation, + Object: t.Object, + }) + } + out = append(out, check) + } + return out +} diff --git a/internal/grpcsrv/interceptors/errormap.go b/internal/grpcsrv/interceptors/errormap.go new file mode 100644 index 00000000..65c47b44 --- /dev/null +++ b/internal/grpcsrv/interceptors/errormap.go @@ -0,0 +1,69 @@ +package interceptors + +import ( + "context" + "errors" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/authorizerdev/authorizer/internal/service" +) + +// kindToCode maps a transport-neutral service.ErrorKind onto the gRPC status +// code. grpc-gateway then derives the REST HTTP status from this code +// (InvalidArgument->400, Unauthenticated->401, PermissionDenied->403, +// NotFound->404, FailedPrecondition->400, Internal->500). +func kindToCode(kind service.ErrorKind) codes.Code { + switch kind { + case service.KindInvalidArgument: + return codes.InvalidArgument + case service.KindUnauthenticated: + return codes.Unauthenticated + case service.KindPermissionDenied: + return codes.PermissionDenied + case service.KindNotFound: + return codes.NotFound + case service.KindFailedPrecondition: + return codes.FailedPrecondition + default: + return codes.Internal + } +} + +// ErrorMap is the innermost unary interceptor. It translates errors returned by +// the handler into proper gRPC status errors so both gRPC and REST clients see +// a meaningful status code instead of codes.Unknown / HTTP 500 for everything. +// +// Placement: this MUST be the last (innermost) interceptor in the chain so it +// wraps the handler directly. Validation failures produced by the protovalidate +// interceptor live one level out and already carry InvalidArgument, so they +// never reach here. Any error that is already a gRPC status (e.g. the +// Unimplemented stubs) is passed through untouched; typed service.Error values +// are mapped by Kind; everything else is treated as Internal. +func ErrorMap() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + resp, err := handler(ctx, req) + if err == nil { + return resp, nil + } + + // Typed service error: map its Kind onto a gRPC code, preserving the + // message the service chose to surface. + var se *service.Error + if errors.As(err, &se) { + return resp, status.Error(kindToCode(se.Kind), se.Error()) + } + + // Already a gRPC status error (Unimplemented stubs, or a status + // produced upstream): leave it as-is. + if _, ok := status.FromError(err); ok { + return resp, err + } + + // Unclassified error: treat as internal. The handler already decided + // what message text is safe to surface, so we forward it verbatim. + return resp, status.Error(codes.Internal, err.Error()) + } +} diff --git a/internal/grpcsrv/interceptors/interceptors_test.go b/internal/grpcsrv/interceptors/interceptors_test.go new file mode 100644 index 00000000..e1d58f1f --- /dev/null +++ b/internal/grpcsrv/interceptors/interceptors_test.go @@ -0,0 +1,157 @@ +package interceptors + +import ( + "bytes" + "context" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// info builds a *grpc.UnaryServerInfo for a fake RPC. The full-method name is +// the only field interceptors actually read. +func info(method string) *grpc.UnaryServerInfo { + return &grpc.UnaryServerInfo{FullMethod: method} +} + +func TestRecovery_TurnsPanicIntoInternal(t *testing.T) { + var buf bytes.Buffer + log := zerolog.New(&buf) + + r := Recovery(&log) + _, err := r(context.Background(), nil, info("/svc/Method"), func(_ context.Context, _ any) (any, error) { + panic("kaboom") + }) + + st, ok := status.FromError(err) + require.True(t, ok, "expected a gRPC status error") + assert.Equal(t, codes.Internal, st.Code()) + assert.Equal(t, "internal server error", st.Message(), "panic detail must not leak to clients") + out := buf.String() + // The stack + type get logged. The recovered VALUE does NOT — security + // audit H2: panic values can carry credentials (Password / RefreshToken + // / OTP / AdminSecret) that must not reach the log stream. + assert.Contains(t, out, "panicked") + assert.Contains(t, out, `"panic_type":"string"`) + assert.NotContains(t, out, "kaboom", + "the panic VALUE must not appear in logs; only its type — see H2") +} + +// TestRecovery_DoesNotLogCredentialBearingPanicValue is the regression test +// for security audit H2: a handler that panics with a value containing +// credentials must NOT have those credentials written to the log stream. +func TestRecovery_DoesNotLogCredentialBearingPanicValue(t *testing.T) { + var buf bytes.Buffer + log := zerolog.New(&buf) + r := Recovery(&log) + _, _ = r(context.Background(), nil, info("/svc/X"), func(_ context.Context, _ any) (any, error) { + // Simulate a handler panicking with a credential-bearing value. + panic("password=hunter2 token=secretXYZ") + }) + out := buf.String() + assert.NotContains(t, out, "hunter2", "panic value must not reach logs") + assert.NotContains(t, out, "secretXYZ", "panic value must not reach logs") + assert.Contains(t, out, `"panic_type":"string"`, "type should still be logged for triage") +} + +func TestRecovery_PassesNormalErrorsThrough(t *testing.T) { + log := zerolog.Nop() + r := Recovery(&log) + want := status.Error(codes.NotFound, "no") + _, err := r(context.Background(), nil, info("/svc/X"), func(_ context.Context, _ any) (any, error) { + return nil, want + }) + assert.Equal(t, want, err) +} + +func TestLogging_OkPath(t *testing.T) { + var buf bytes.Buffer + log := zerolog.New(&buf) + mw := Logging(&log) + _, err := mw(context.Background(), nil, info("/svc/Foo"), func(_ context.Context, _ any) (any, error) { + return "ok", nil + }) + require.NoError(t, err) + out := buf.String() + assert.Contains(t, out, `"method":"/svc/Foo"`) + assert.Contains(t, out, `"code":"OK"`) + assert.Contains(t, out, `"level":"info"`) +} + +func TestLogging_ErrorPathRaisesLevel(t *testing.T) { + var buf bytes.Buffer + log := zerolog.New(&buf) + mw := Logging(&log) + _, _ = mw(context.Background(), nil, info("/svc/Bad"), func(_ context.Context, _ any) (any, error) { + return nil, status.Error(codes.Internal, "boom") + }) + out := buf.String() + assert.Contains(t, out, `"code":"Internal"`) + assert.Contains(t, out, `"level":"error"`, "Internal/Unknown/DataLoss must raise log level to error") +} + +func TestLogging_PermissionDeniedIsWarn(t *testing.T) { + var buf bytes.Buffer + log := zerolog.New(&buf) + mw := Logging(&log) + _, _ = mw(context.Background(), nil, info("/svc/X"), func(_ context.Context, _ any) (any, error) { + return nil, status.Error(codes.PermissionDenied, "no") + }) + assert.Contains(t, buf.String(), `"level":"warn"`, "non-Internal failures must log at warn, not error") +} + +func TestValidate_RejectsBadRequest(t *testing.T) { + mw, err := Validate() + require.NoError(t, err) + + // RevokeRequest enforces refresh_token min_len=1 via buf.validate.field + // — an empty string should fail the interceptor before any handler runs. + req := &authorizerv1.RevokeRequest{RefreshToken: ""} + _, err = mw(context.Background(), req, info("/authorizer.v1.Authorizer/Revoke"), func(_ context.Context, _ any) (any, error) { + t.Fatal("handler must NOT run for an invalid request") + return nil, nil + }) + st, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, codes.InvalidArgument, st.Code()) +} + +func TestValidate_AllowsValidRequest(t *testing.T) { + mw, err := Validate() + require.NoError(t, err) + called := false + _, err = mw(context.Background(), &authorizerv1.MetaRequest{}, info("/authorizer.v1.Authorizer/Meta"), func(_ context.Context, _ any) (any, error) { + called = true + return &authorizerv1.MetaResponse{}, nil + }) + require.NoError(t, err) + assert.True(t, called, "valid request must reach the handler") +} + +func TestValidate_NonProtoRequestPassesThrough(t *testing.T) { + mw, err := Validate() + require.NoError(t, err) + _, err = mw(context.Background(), "not-a-proto", info("/svc/X"), func(_ context.Context, _ any) (any, error) { + return nil, nil + }) + require.NoError(t, err, "non-proto requests must not be rejected by the validator") +} + +// TestValidate_PreservesInvariant guards against regressions where someone +// makes Validate() return a non-functional middleware (e.g. by reordering +// the protovalidate.New() call). If the validator itself fails to build, +// callers must learn about it at startup, not at first request. +func TestValidate_BuildsCleanly(t *testing.T) { + mw, err := Validate() + require.NoError(t, err) + require.NotNil(t, mw) + // Sanity check: the returned interceptor type is what gRPC expects. + _ = grpc.UnaryServerInterceptor(mw) +} diff --git a/internal/grpcsrv/interceptors/logging.go b/internal/grpcsrv/interceptors/logging.go new file mode 100644 index 00000000..67226b0c --- /dev/null +++ b/internal/grpcsrv/interceptors/logging.go @@ -0,0 +1,40 @@ +package interceptors + +import ( + "context" + "time" + + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Logging returns a unary interceptor that emits one structured log line per +// RPC at info level (or error/warn when the status code reflects failure). +// Method name, duration, and gRPC code are always present. +func Logging(log *zerolog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + start := time.Now() + resp, err := handler(ctx, req) + dur := time.Since(start) + + code := status.Code(err) + evt := log.Info() + switch code { + case codes.OK: + // stays info + case codes.Internal, codes.Unknown, codes.DataLoss: + evt = log.Error() + default: + evt = log.Warn() + } + evt. + Str("method", info.FullMethod). + Str("code", code.String()). + Dur("duration", dur). + Err(err). + Msg("grpc") + return resp, err + } +} diff --git a/internal/grpcsrv/interceptors/recovery.go b/internal/grpcsrv/interceptors/recovery.go new file mode 100644 index 00000000..3df2d04f --- /dev/null +++ b/internal/grpcsrv/interceptors/recovery.go @@ -0,0 +1,46 @@ +// Package interceptors contains the gRPC server interceptors shared across +// Authorizer services. They run in this order (outermost first): +// +// recovery → logging → validate → ... (auth, permission, audit — added per service in later PRs) +// +// Recovery is outermost so it catches panics raised by anything later, and +// converts them to a clean codes.Internal status instead of crashing the +// server. +package interceptors + +import ( + "context" + "fmt" + "runtime/debug" + + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Recovery returns a unary interceptor that converts handler panics into a +// codes.Internal error and logs the stack at error level. The stack stays +// server-side — clients only see a generic "internal error" message. +// +// Security: the panic value is logged as TYPE only, never its full content. +// A handler can panic with a request struct that includes credentials +// (Password, RefreshToken, OTP, AdminSecret, ...); dumping the value via +// .Interface() would write those credentials to the log stream verbatim. +// Logging just the type lets ops correlate the panic with the stack without +// exposing payload fields. (Security audit finding H2.) +func Recovery(log *zerolog.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + defer func() { + if r := recover(); r != nil { + log.Error(). + Str("method", info.FullMethod). + Str("panic_type", fmt.Sprintf("%T", r)). + Bytes("stack", debug.Stack()). + Msg("gRPC handler panicked") + err = status.Error(codes.Internal, "internal server error") + } + }() + return handler(ctx, req) + } +} diff --git a/internal/grpcsrv/interceptors/validate.go b/internal/grpcsrv/interceptors/validate.go new file mode 100644 index 00000000..6226888e --- /dev/null +++ b/internal/grpcsrv/interceptors/validate.go @@ -0,0 +1,32 @@ +package interceptors + +import ( + "context" + + "buf.build/go/protovalidate" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Validate returns a unary interceptor that runs protovalidate on every +// inbound request that's a proto.Message. Failures convert to +// codes.InvalidArgument with a human-readable detail. +// +// The validator is built once at startup and shared across requests +// (protovalidate.Validator is safe for concurrent use). +func Validate() (grpc.UnaryServerInterceptor, error) { + v, err := protovalidate.New() + if err != nil { + return nil, err + } + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + if msg, ok := req.(proto.Message); ok { + if err := v.Validate(msg); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "%v", err) + } + } + return handler(ctx, req) + }, nil +} diff --git a/internal/grpcsrv/server.go b/internal/grpcsrv/server.go new file mode 100644 index 00000000..a902dbe4 --- /dev/null +++ b/internal/grpcsrv/server.go @@ -0,0 +1,111 @@ +// Package grpcsrv builds and runs the Authorizer gRPC server. It registers +// every public-API service (real or stubbed), enables reflection, exposes +// the standard gRPC health checking protocol, and applies the shared +// interceptor chain. +package grpcsrv + +import ( + "context" + "net" + + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthv1 "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" + + "github.com/authorizerdev/authorizer/internal/config" + "github.com/authorizerdev/authorizer/internal/grpcsrv/handlers" + "github.com/authorizerdev/authorizer/internal/grpcsrv/interceptors" + "github.com/authorizerdev/authorizer/internal/service" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// Dependencies is the minimum set the gRPC server needs. +type Dependencies struct { + Log *zerolog.Logger + Config *config.Config + ServiceProvider service.Provider +} + +// Server wraps a *grpc.Server plus its listener address. +type Server struct { + deps *Dependencies + addr string + srv *grpc.Server + health *health.Server +} + +// New constructs a configured gRPC server. The server is not yet listening; +// call Run to start serving. +func New(addr string, deps *Dependencies) (*Server, error) { + validate, err := interceptors.Validate() + if err != nil { + return nil, err + } + + srv := grpc.NewServer( + grpc.ChainUnaryInterceptor( + interceptors.Recovery(deps.Log), + interceptors.Logging(deps.Log), + validate, + // Innermost: wraps the handler directly so it can translate typed + // service.Error values into proper gRPC status codes. Must stay + // last — see interceptors.ErrorMap docs. + interceptors.ErrorMap(), + ), + ) + + // Register the single AuthorizerService. AuthorizerHandler embeds + // UnimplementedAuthorizerServiceServer, so any RPC whose method has + // not yet been migrated returns codes.Unimplemented. Migrated methods + // (today: Meta) override the unimplemented stubs. + authorizerv1.RegisterAuthorizerServiceServer(srv, &handlers.AuthorizerHandler{Service: deps.ServiceProvider}) + + // gRPC health checking protocol (used by k8s grpc-probe and similar). + hs := health.NewServer() + hs.SetServingStatus("", healthv1.HealthCheckResponse_SERVING) + healthv1.RegisterHealthServer(srv, hs) + + // Reflection is gated on a config flag so prod deployments can lock it + // off, but defaults on for dev/test parity with the playground. + if deps.Config.EnableGRPCReflection { + reflection.Register(srv) + } + + return &Server{ + deps: deps, + addr: addr, + srv: srv, + health: hs, + }, nil +} + +// GRPCServer exposes the underlying *grpc.Server. Used by the in-process +// REST gateway mount to dial via bufconn during tests. +func (s *Server) GRPCServer() *grpc.Server { return s.srv } + +// Run starts the listener and blocks until ctx is cancelled or Serve errors. +// On context cancellation, the server is gracefully stopped (existing RPCs +// finish, no new ones accepted). +func (s *Server) Run(ctx context.Context) error { + lis, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + s.deps.Log.Info().Str("addr", s.addr).Msg("Starting gRPC server") + + errCh := make(chan error, 1) + go func() { errCh <- s.srv.Serve(lis) }() + + select { + case <-ctx.Done(): + s.deps.Log.Info().Msg("gRPC shutdown signal received, draining") + s.health.Shutdown() + s.srv.GracefulStop() + return nil + case err := <-errCh: + return err + } +} diff --git a/internal/grpcsrv/transport/grpc_metadata.go b/internal/grpcsrv/transport/grpc_metadata.go new file mode 100644 index 00000000..118d48c8 --- /dev/null +++ b/internal/grpcsrv/transport/grpc_metadata.go @@ -0,0 +1,142 @@ +// Package transport bridges between gRPC's incoming metadata / outgoing +// trailers and the service layer's transport-agnostic RequestMetadata / +// ResponseSideEffects. +// +// gRPC has no native cookie concept; cookies in ResponseSideEffects are +// serialised to `Set-Cookie` metadata entries. grpc-gateway promotes those +// into real `Set-Cookie` response headers when the call came in via REST. +// Pure-gRPC clients can read them via the response trailers or ignore them. +package transport + +import ( + "context" + "net/http" + "net/url" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/authorizerdev/authorizer/internal/service" +) + +// MetaFromGRPC builds a RequestMetadata from a gRPC context. Headers +// populated by grpc-gateway (`grpcgateway-*` prefix) are honored so the +// service sees the same host/IP/UA whether the call came via gRPC directly +// or via REST through the gateway. +func MetaFromGRPC(ctx context.Context) service.RequestMetadata { + md, _ := metadata.FromIncomingContext(ctx) + meta := service.RequestMetadata{ + HostURL: firstHeader(md, "x-authorizer-url", "grpcgateway-x-authorizer-url"), + IPAddress: firstHeader(md, "x-forwarded-for", "grpcgateway-x-forwarded-for", "x-real-ip"), + UserAgent: firstHeader(md, "grpcgateway-user-agent", "user-agent"), + AuthorizationHeader: firstHeader(md, "authorization", "grpcgateway-authorization"), + Cookies: cookiesFromMetadata(md), + } + // Default the host URL when no header was set (pure-gRPC caller, no + // proxy headers). The :authority pseudo-header is the gRPC equivalent + // of Host; use it as a fallback. + if meta.HostURL == "" { + if authority := firstHeader(md, ":authority"); authority != "" { + meta.HostURL = "http://" + authority + } + } + + // Synthesize an *http.Request mirroring the extracted metadata. Several + // migrated service methods (Profile, Permissions, Logout, Session, + // ValidateSession) still hand a gin.Context shim to TokenProvider helpers + // that read Request.Header / Request.Cookies(). Without a non-nil Request + // those helpers dereference nil and panic. Building the request here keeps + // the gRPC/REST path behaving exactly like the gin path. + meta.Request = synthRequest(meta) + return meta +} + +// synthRequest reconstructs a minimal *http.Request from the transport-neutral +// RequestMetadata so the legacy gin-shim helpers (which read Header, Cookies, +// Host, and RemoteAddr) work identically over gRPC/REST and direct HTTP. +func synthRequest(meta service.RequestMetadata) *http.Request { + req := &http.Request{ + Header: http.Header{}, + URL: &url.URL{}, + } + if meta.AuthorizationHeader != "" { + req.Header.Set("Authorization", meta.AuthorizationHeader) + } + if meta.UserAgent != "" { + req.Header.Set("User-Agent", meta.UserAgent) + } + if meta.IPAddress != "" { + req.Header.Set("X-Forwarded-For", meta.IPAddress) + req.RemoteAddr = meta.IPAddress + } + if meta.HostURL != "" { + req.Header.Set("X-Authorizer-URL", meta.HostURL) + if u, err := url.Parse(meta.HostURL); err == nil && u.Host != "" { + req.Host = u.Host + } + } + for _, c := range meta.Cookies { + if c != nil { + req.AddCookie(c) + } + } + return req +} + +// ApplyToGRPC writes the response side-effects to the outgoing gRPC stream. +// Every cookie becomes its own `Set-Cookie` metadata entry — preserving +// multi-cookie responses (e.g. host-scoped + domain-scoped session pair). +// grpc-gateway promotes the metadata back to real `Set-Cookie` HTTP headers. +// A nil receiver is a no-op. +func ApplyToGRPC(ctx context.Context, side *service.ResponseSideEffects) error { + if side == nil || len(side.Cookies) == 0 { + return nil + } + // grpc-gateway honours the per-RPC `Set-Cookie` metadata when prefixed + // `Grpc-Metadata-Set-Cookie` or under the canonical header. Use + // metadata.Pairs equivalents: same key, repeated values. + header := http.CanonicalHeaderKey("Set-Cookie") + md := metadata.MD{} + for _, c := range side.Cookies { + if c == nil { + continue + } + md.Append(header, c.String()) + } + if len(md) == 0 { + return nil + } + return grpc.SendHeader(ctx, md) +} + +func firstHeader(md metadata.MD, keys ...string) string { + for _, k := range keys { + if vs := md.Get(k); len(vs) > 0 { + return vs[0] + } + } + return "" +} + +// cookiesFromMetadata parses Cookie header(s) supplied via gRPC metadata. +// grpc-gateway forwards browser cookies as the `grpcgateway-cookie` key; +// pure-gRPC clients can set `cookie` directly. Multiple Cookie headers are +// concatenated (semicolon-separated per RFC 6265). +func cookiesFromMetadata(md metadata.MD) []*http.Cookie { + var raw []string + raw = append(raw, md.Get("grpcgateway-cookie")...) + raw = append(raw, md.Get("cookie")...) + if len(raw) == 0 { + return nil + } + // http.Request.Cookies parses the Cookie header for us. Synthesize a + // minimal request rather than re-implementing the cookie grammar. + req := &http.Request{Header: http.Header{}} + for _, line := range raw { + // One header may contain multiple cookies separated by "; ". + // http.Header.Add preserves the line; cookies are parsed downstream. + req.Header.Add("Cookie", strings.TrimSpace(line)) + } + return req.Cookies() +} diff --git a/internal/grpcsrv/transport/grpc_metadata_test.go b/internal/grpcsrv/transport/grpc_metadata_test.go new file mode 100644 index 00000000..cc92db11 --- /dev/null +++ b/internal/grpcsrv/transport/grpc_metadata_test.go @@ -0,0 +1,107 @@ +package transport + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" + + "github.com/authorizerdev/authorizer/internal/service" +) + +func TestMetaFromGRPC_ExtractsAllSignals(t *testing.T) { + md := metadata.New(map[string]string{ + "grpcgateway-x-authorizer-url": "https://auth.example.com", + "grpcgateway-x-forwarded-for": "10.1.2.3", + "grpcgateway-user-agent": "browser/1.0", + "grpcgateway-authorization": "Bearer abc", + "grpcgateway-cookie": "authorizer_session=abc; mfa=xyz", + }) + ctx := metadata.NewIncomingContext(context.Background(), md) + meta := MetaFromGRPC(ctx) + assert.Equal(t, "https://auth.example.com", meta.HostURL) + assert.Equal(t, "10.1.2.3", meta.IPAddress) + assert.Equal(t, "browser/1.0", meta.UserAgent) + assert.Equal(t, "Bearer abc", meta.AuthorizationHeader) + require.Len(t, meta.Cookies, 2) + cookieValues := map[string]string{} + for _, c := range meta.Cookies { + cookieValues[c.Name] = c.Value + } + assert.Equal(t, "abc", cookieValues["authorizer_session"]) + assert.Equal(t, "xyz", cookieValues["mfa"]) +} + +func TestMetaFromGRPC_FallsBackToAuthority(t *testing.T) { + md := metadata.New(map[string]string{":authority": "auth.example.com"}) + ctx := metadata.NewIncomingContext(context.Background(), md) + meta := MetaFromGRPC(ctx) + assert.Equal(t, "http://auth.example.com", meta.HostURL) +} + +func TestMetaFromGRPC_NoMetadata(t *testing.T) { + meta := MetaFromGRPC(context.Background()) + // All transport-derived signals are empty with no metadata... + assert.Empty(t, meta.HostURL) + assert.Empty(t, meta.IPAddress) + assert.Empty(t, meta.UserAgent) + assert.Empty(t, meta.AuthorizationHeader) + assert.Empty(t, meta.Cookies) + // ...but Request is always synthesized (non-nil) so the gin-shim helpers + // in the migrated service methods never dereference a nil *http.Request. + require.NotNil(t, meta.Request) + assert.Empty(t, meta.Request.Header.Get("Authorization")) +} + +func TestMetaFromGRPC_SynthesizesRequestFromSignals(t *testing.T) { + md := metadata.New(map[string]string{ + "grpcgateway-x-authorizer-url": "https://auth.example.com", + "grpcgateway-x-forwarded-for": "10.1.2.3", + "grpcgateway-user-agent": "browser/1.0", + "grpcgateway-authorization": "Bearer abc", + "grpcgateway-cookie": "authorizer_session=abc", + }) + meta := MetaFromGRPC(metadata.NewIncomingContext(context.Background(), md)) + require.NotNil(t, meta.Request) + // The synthesized request must carry the bearer + cookie so legacy + // TokenProvider helpers (which read Request.Header / Request.Cookies()) + // behave identically over gRPC/REST and direct HTTP. + assert.Equal(t, "Bearer abc", meta.Request.Header.Get("Authorization")) + assert.Equal(t, "browser/1.0", meta.Request.Header.Get("User-Agent")) + assert.Equal(t, "auth.example.com", meta.Request.Host) + c, err := meta.Request.Cookie("authorizer_session") + require.NoError(t, err) + assert.Equal(t, "abc", c.Value) +} + +func TestCookiesFromMetadata_MultipleHeaders(t *testing.T) { + md := metadata.MD{} + md.Append("grpcgateway-cookie", "a=1; b=2") + md.Append("grpcgateway-cookie", "c=3") + cookies := cookiesFromMetadata(md) + require.Len(t, cookies, 3) + got := map[string]string{} + for _, c := range cookies { + got[c.Name] = c.Value + } + assert.Equal(t, map[string]string{"a": "1", "b": "2", "c": "3"}, got) +} + +func TestCookiesFromMetadata_NoCookies(t *testing.T) { + assert.Nil(t, cookiesFromMetadata(metadata.MD{})) +} + +func TestApplyToGRPC_NilSafe(t *testing.T) { + // nil receiver / empty cookies must not error. + assert.NoError(t, ApplyToGRPC(context.Background(), nil)) + assert.NoError(t, ApplyToGRPC(context.Background(), &service.ResponseSideEffects{})) + assert.NoError(t, ApplyToGRPC(context.Background(), &service.ResponseSideEffects{Cookies: []*http.Cookie{nil}})) +} + +// Note: ApplyToGRPC's success path uses grpc.SendHeader which requires a +// real *grpc.ServerStream / handler context. That's covered end-to-end by +// the integration tests in internal/integration_tests where cookies emitted +// by a CreateSession handler land in the REST response. diff --git a/internal/http_handlers/graphql.go b/internal/http_handlers/graphql.go index ab098cf9..23081882 100644 --- a/internal/http_handlers/graphql.go +++ b/internal/http_handlers/graphql.go @@ -218,6 +218,7 @@ func (h *httpProvider) GraphqlHandler() gin.HandlerFunc { SMSProvider: h.SMSProvider, StorageProvider: h.StorageProvider, TokenProvider: h.TokenProvider, + ServiceProvider: h.ServiceProvider, AuthzEngine: h.AuthzEngine, }) if err != nil { diff --git a/internal/http_handlers/provider.go b/internal/http_handlers/provider.go index a7c4ca71..195ae6d3 100644 --- a/internal/http_handlers/provider.go +++ b/internal/http_handlers/provider.go @@ -13,6 +13,7 @@ import ( "github.com/authorizerdev/authorizer/internal/memory_store" "github.com/authorizerdev/authorizer/internal/oauth" "github.com/authorizerdev/authorizer/internal/rate_limit" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" "github.com/authorizerdev/authorizer/internal/token" @@ -43,6 +44,9 @@ type Dependencies struct { OAuthProvider oauth.Provider // RateLimitProvider is used for per-IP rate limiting RateLimitProvider rate_limit.Provider + // ServiceProvider hosts the transport-agnostic public-API operations. + // Migrated GraphQL resolvers delegate here. + ServiceProvider service.Provider // AuthzEngine is the fine-grained authorization (FGA) engine. // It is nil unless an FGA store is configured (--fga-store). AuthzEngine engine.AuthorizationEngine diff --git a/internal/integration_tests/fga_test.go b/internal/integration_tests/fga_test.go index f68bb384..cc124b59 100644 --- a/internal/integration_tests/fga_test.go +++ b/internal/integration_tests/fga_test.go @@ -31,6 +31,7 @@ import ( "github.com/authorizerdev/authorizer/internal/oauth" "github.com/authorizerdev/authorizer/internal/rate_limit" "github.com/authorizerdev/authorizer/internal/refs" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" "github.com/authorizerdev/authorizer/internal/token" @@ -82,6 +83,22 @@ func initFGATestSetup(t *testing.T, cfg *config.Config) (*testSetup, engine.Auth ) require.NoError(t, err) + // Transport-agnostic service layer for migrated public ops. The FGA-gated + // ops (session/validate_*/check_permissions/list_permissions) now live here, + // so the engine MUST be injected or those resolvers nil-pointer panic. + serviceProvider, err := service.New(cfg, &service.Dependencies{ + Log: &logger, + AuditProvider: auditProvider, + EmailProvider: emailProvider, + EventsProvider: eventsProvider, + MemoryStoreProvider: memoryStoreProvider, + SMSProvider: smsProvider, + StorageProvider: storageProvider, + TokenProvider: tokenProvider, + AuthzEngine: fgaEngine, + }) + require.NoError(t, err) + gqlProvider, err := graphql.New(cfg, &graphql.Dependencies{ Log: &logger, AuditProvider: auditProvider, @@ -93,6 +110,7 @@ func initFGATestSetup(t *testing.T, cfg *config.Config) (*testSetup, engine.Auth StorageProvider: storageProvider, TokenProvider: tokenProvider, AuthzEngine: fgaEngine, + ServiceProvider: serviceProvider, }) require.NoError(t, err) @@ -109,6 +127,7 @@ func initFGATestSetup(t *testing.T, cfg *config.Config) (*testSetup, engine.Auth RateLimitProvider: rateLimitProvider, OAuthProvider: oauthProvider, AuthzEngine: fgaEngine, + ServiceProvider: serviceProvider, }) require.NoError(t, err) @@ -143,6 +162,7 @@ func initFGATestSetup(t *testing.T, cfg *config.Config) (*testSetup, engine.Auth MemoryStoreProvider: memoryStoreProvider, AuthenticatorProvider: authProvider, TokenProvider: tokenProvider, + ServiceProvider: serviceProvider, }, fgaEngine } diff --git a/internal/integration_tests/grpc_meta_test.go b/internal/integration_tests/grpc_meta_test.go new file mode 100644 index 00000000..56bb5605 --- /dev/null +++ b/internal/integration_tests/grpc_meta_test.go @@ -0,0 +1,58 @@ +package integration_tests + +import ( + "context" + "net" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + + "github.com/authorizerdev/authorizer/internal/grpcsrv" + "github.com/authorizerdev/authorizer/internal/service" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// TestGRPCMeta exercises AuthorizerService.Meta end-to-end over a bufconn +// in-process gRPC channel. Validates the consolidated single-service +// design: proto → handler → service.Meta → response projection. +func TestGRPCMeta(t *testing.T) { + cfg := getTestConfig() + cfg.ClientID = "test-client" + + log := zerolog.New(zerolog.NewTestWriter(t)).With().Timestamp().Logger() + + svc, err := service.New(cfg, &service.Dependencies{Log: &log}) + require.NoError(t, err) + + srv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: &log, + Config: cfg, + ServiceProvider: svc, + }) + require.NoError(t, err) + + lis := bufconn.Listen(1 << 20) + t.Cleanup(func() { _ = lis.Close() }) + go func() { _ = srv.GRPCServer().Serve(lis) }() + t.Cleanup(srv.GRPCServer().GracefulStop) + + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + + client := authorizerv1.NewAuthorizerServiceClient(conn) + resp, err := client.Meta(context.Background(), &authorizerv1.MetaRequest{}) + require.NoError(t, err) + require.NotNil(t, resp.Meta) + require.Equal(t, "test-client", resp.Meta.ClientId) + require.NotEmpty(t, resp.Meta.Version) +} diff --git a/internal/integration_tests/grpc_permissions_test.go b/internal/integration_tests/grpc_permissions_test.go new file mode 100644 index 00000000..35cf8af0 --- /dev/null +++ b/internal/integration_tests/grpc_permissions_test.go @@ -0,0 +1,79 @@ +package integration_tests + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" + "github.com/authorizerdev/authorizer/internal/service" +) + +// TestGRPCCheckPermissionsFailClosed exercises AuthorizerService.CheckPermissions +// over the in-process bufconn channel when no FGA engine is configured (the +// default test setup wires no AuthzEngine). The service fails closed with +// service.ErrFgaNotEnabled before any subject/auth resolution; that plain error +// flows through the ErrorMap interceptor as codes.FailedPrecondition, carrying the +// verbatim "fine-grained authorization is not enabled" message. +// +// Ordering matters: the engine-nil guard is the FIRST check in the service +// method — it runs before resolveFgaSubject — so even an unauthenticated caller +// gets ErrFgaNotEnabled, NOT Unauthenticated. This test pins that order. +func TestGRPCCheckPermissionsFailClosed(t *testing.T) { + conn := bootGRPCBufconn(t) + c := authorizerv1.NewAuthorizerServiceClient(conn) + + // Unauthenticated call (no metadata / bearer): the engine check still fires + // first, so we expect the FGA-not-enabled error, not Unauthenticated. + _, err := c.CheckPermissions(context.Background(), &authorizerv1.CheckPermissionsRequest{ + Checks: []*authorizerv1.PermissionCheckInput{ + {Relation: "can_view", Object: "document:1"}, + }, + }) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "expected a gRPC status error") + assert.Equal(t, codes.FailedPrecondition, st.Code(), + "ErrFgaNotEnabled is a typed FailedPrecondition service error") + assert.Equal(t, service.ErrFgaNotEnabled.Error(), st.Message(), + "the FGA-disabled message must surface verbatim") +} + +// TestGRPCListPermissionsFailClosed mirrors the CheckPermissions fail-closed +// behavior for ListPermissions: with no FGA engine the engine-nil guard denies +// up front with service.ErrFgaNotEnabled -> codes.FailedPrecondition. +func TestGRPCListPermissionsFailClosed(t *testing.T) { + conn := bootGRPCBufconn(t) + c := authorizerv1.NewAuthorizerServiceClient(conn) + + _, err := c.ListPermissions(context.Background(), &authorizerv1.ListPermissionsRequest{}) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "expected a gRPC status error") + assert.Equal(t, codes.FailedPrecondition, st.Code(), + "ErrFgaNotEnabled is a typed FailedPrecondition service error") + assert.Equal(t, service.ErrFgaNotEnabled.Error(), st.Message()) +} + +// TestGRPCCheckPermissionsEmptyChecksRejected verifies the protovalidate +// interceptor enforces CheckPermissionsRequest.checks min_items=1. The validate +// interceptor sits OUTSIDE the handler, so an empty checks list is rejected with +// codes.InvalidArgument before the handler (and thus before the engine-nil +// guard) ever runs — the request never reaches the service layer. +func TestGRPCCheckPermissionsEmptyChecksRejected(t *testing.T) { + conn := bootGRPCBufconn(t) + c := authorizerv1.NewAuthorizerServiceClient(conn) + + _, err := c.CheckPermissions(context.Background(), &authorizerv1.CheckPermissionsRequest{ + Checks: nil, // empty -> violates min_items: 1 + }) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok, "expected a gRPC status error") + assert.Equal(t, codes.InvalidArgument, st.Code(), + "empty checks must be rejected by the protovalidate interceptor, not the handler") +} diff --git a/internal/integration_tests/grpc_surface_test.go b/internal/integration_tests/grpc_surface_test.go new file mode 100644 index 00000000..e07a6bbe --- /dev/null +++ b/internal/integration_tests/grpc_surface_test.go @@ -0,0 +1,167 @@ +package integration_tests + +import ( + "context" + "net" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + healthv1 "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + + "github.com/authorizerdev/authorizer/internal/grpcsrv" + "github.com/authorizerdev/authorizer/internal/service" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// bootGRPCBufconn builds a gRPC server identical to the production one, +// served over an in-process bufconn. Returns a dialed *grpc.ClientConn the +// test uses to issue real RPCs. +func bootGRPCBufconn(t *testing.T) *grpc.ClientConn { + t.Helper() + cfg := getTestConfig() + cfg.ClientID = "test-client" + log := zerolog.New(zerolog.NewTestWriter(t)).With().Timestamp().Logger() + + svc, err := service.New(cfg, &service.Dependencies{Log: &log}) + require.NoError(t, err) + srv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{Log: &log, Config: cfg, ServiceProvider: svc}) + require.NoError(t, err) + + lis := bufconn.Listen(1 << 20) + t.Cleanup(func() { _ = lis.Close() }) + go func() { _ = srv.GRPCServer().Serve(lis) }() + t.Cleanup(srv.GRPCServer().GracefulStop) + + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + return conn +} + +// TestAuthorizerServiceStubsReturnUnimplemented locks down the contract for +// every not-yet-migrated method on the consolidated AuthorizerService. +// +// The service exposes 20 RPCs. Real today (10): Signup, Meta, Profile, Logout, +// Revoke, Session, ValidateJwtToken, ValidateSession, CheckPermissions, +// ListPermissions (the last two cover the former single Permissions RPC and are +// exercised in grpc_permissions_test.go / rest_permissions_test.go). The +// remaining 10 are still stubs and MUST return codes.Unimplemented — that +// invariant is what this test guards. As each handler is wired up, drop its +// entry below. +func TestAuthorizerServiceStubsReturnUnimplemented(t *testing.T) { + conn := bootGRPCBufconn(t) + ctx := context.Background() + c := authorizerv1.NewAuthorizerServiceClient(conn) + + type call func(context.Context) error + cases := map[string]call{ + "Login": func(c0 context.Context) error { + _, err := c.Login(c0, &authorizerv1.LoginRequest{Password: "p"}) + return err + }, + "MagicLinkLogin": func(c0 context.Context) error { + _, err := c.MagicLinkLogin(c0, &authorizerv1.MagicLinkLoginRequest{Email: "x@example.com"}) + return err + }, + "VerifyEmail": func(c0 context.Context) error { + _, err := c.VerifyEmail(c0, &authorizerv1.VerifyEmailRequest{Token: "t"}) + return err + }, + "ResendVerifyEmail": func(c0 context.Context) error { + _, err := c.ResendVerifyEmail(c0, &authorizerv1.ResendVerifyEmailRequest{Email: "x@example.com", Identifier: "id"}) + return err + }, + "VerifyOtp": func(c0 context.Context) error { + _, err := c.VerifyOtp(c0, &authorizerv1.VerifyOtpRequest{Email: "x@example.com", Otp: "1"}) + return err + }, + "ResendOtp": func(c0 context.Context) error { + _, err := c.ResendOtp(c0, &authorizerv1.ResendOtpRequest{Email: "x@example.com"}) + return err + }, + "ForgotPassword": func(c0 context.Context) error { + _, err := c.ForgotPassword(c0, &authorizerv1.ForgotPasswordRequest{Email: "x@example.com"}) + return err + }, + "ResetPassword": func(c0 context.Context) error { + _, err := c.ResetPassword(c0, &authorizerv1.ResetPasswordRequest{Token: "t", Password: "p", ConfirmPassword: "p"}) + return err + }, + "UpdateProfile": func(c0 context.Context) error { + _, err := c.UpdateProfile(c0, &authorizerv1.UpdateProfileRequest{}) + return err + }, + "DeactivateAccount": func(c0 context.Context) error { + _, err := c.DeactivateAccount(c0, &authorizerv1.DeactivateAccountRequest{}) + return err + }, + } + + for name, fn := range cases { + t.Run(name, func(t *testing.T) { + err := fn(ctx) + require.Error(t, err) + st, ok := status.FromError(err) + require.True(t, ok) + assert.Equal(t, codes.Unimplemented, st.Code(), + "stub for AuthorizerService.%s should return Unimplemented until its handler is wired", name) + }) + } +} + +// TestAuthorizerServicePermissionRPCsAreImplemented is the positive counterpart +// to the stub contract: CheckPermissions and ListPermissions replaced the old +// Permissions RPC and are wired to the service layer. They MUST NOT report +// Unimplemented. With no FGA engine configured (the default test setup) they +// fail closed — but with a real status code, never codes.Unimplemented. +func TestAuthorizerServicePermissionRPCsAreImplemented(t *testing.T) { + conn := bootGRPCBufconn(t) + ctx := context.Background() + c := authorizerv1.NewAuthorizerServiceClient(conn) + + type call func(context.Context) error + cases := map[string]call{ + "CheckPermissions": func(c0 context.Context) error { + _, err := c.CheckPermissions(c0, &authorizerv1.CheckPermissionsRequest{ + Checks: []*authorizerv1.PermissionCheckInput{{Relation: "can_view", Object: "document:1"}}, + }) + return err + }, + "ListPermissions": func(c0 context.Context) error { + _, err := c.ListPermissions(c0, &authorizerv1.ListPermissionsRequest{}) + return err + }, + } + + for name, fn := range cases { + t.Run(name, func(t *testing.T) { + err := fn(ctx) + require.Error(t, err, "permission RPC should fail closed without an FGA engine") + st, ok := status.FromError(err) + require.True(t, ok) + assert.NotEqual(t, codes.Unimplemented, st.Code(), + "AuthorizerService.%s is implemented and must not return Unimplemented", name) + }) + } +} + +// TestGRPCHealthCheckProtocol exercises the standard grpc.health.v1.Health +// service that the gRPC server registers for k8s readiness probes. +func TestGRPCHealthCheckProtocol(t *testing.T) { + conn := bootGRPCBufconn(t) + resp, err := healthv1.NewHealthClient(conn).Check(context.Background(), &healthv1.HealthCheckRequest{}) + require.NoError(t, err) + assert.Equal(t, healthv1.HealthCheckResponse_SERVING, resp.Status) +} diff --git a/internal/integration_tests/mcp_stubs_test.go b/internal/integration_tests/mcp_stubs_test.go new file mode 100644 index 00000000..590a8096 --- /dev/null +++ b/internal/integration_tests/mcp_stubs_test.go @@ -0,0 +1,74 @@ +package integration_tests + +import ( + "context" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/internal/grpcsrv" + authmcp "github.com/authorizerdev/authorizer/internal/mcp" + "github.com/authorizerdev/authorizer/internal/service" +) + +// TestMCPToolErrorSurfacesAsIsErrorResult verifies that when the underlying +// gRPC handler returns a non-OK status, the MCP server surfaces it as a +// CallToolResult{IsError:true} (tool-level error) rather than as a +// JSON-RPC protocol error. This is the MCP-spec way to give the LLM +// actionable text it can react to (vs aborting the whole exchange). +// +// We exercise this by calling `check_permissions`: with no FGA engine wired +// in the test config, the CheckPermissions handler fails closed at the +// service layer with "fine-grained authorization is not enabled" +// (service.ErrFgaNotEnabled). That gRPC error must reach the client as a +// tool-level error (IsError:true) carrying the message as text, not as a +// JSON-RPC protocol error — proving the fail-closed gate surfaces cleanly +// and auditably to the MCP host. +func TestMCPToolErrorSurfacesAsIsErrorResult(t *testing.T) { + cfg := getTestConfig() + cfg.ClientID = "test-client" + log := zerolog.New(zerolog.NewTestWriter(t)).With().Timestamp().Logger() + + svc, err := service.New(cfg, &service.Dependencies{Log: &log}) + require.NoError(t, err) + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{Log: &log, Config: cfg, ServiceProvider: svc}) + require.NoError(t, err) + // Note: opts.Bearer deliberately empty — the server runs anonymously, + // so identity-bearing tools must fail with a clean tool error. + mcpSrv, err := authmcp.New(&log, grpcSrv.GRPCServer(), authmcp.Options{Name: "authorizer-test", Version: "v0"}) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + cTransport, sTransport := mcp.NewInMemoryTransports() + serverSession, err := mcpSrv.MCPServer().Connect(ctx, sTransport, nil) + require.NoError(t, err) + defer serverSession.Close() + + client := mcp.NewClient(&mcp.Implementation{Name: "test", Version: "v0"}, nil) + clientSession, err := client.Connect(ctx, cTransport, nil) + require.NoError(t, err) + defer clientSession.Close() + + res, err := clientSession.CallTool(ctx, &mcp.CallToolParams{ + Name: "check_permissions", + // At least one check is required by the proto validation; the call + // gets far enough to hit the fail-closed FGA gate in the service. + Arguments: map[string]any{ + "checks": []map[string]any{ + {"relation": "can_view", "object": "document:1"}, + }, + }, + }) + require.NoError(t, err, "tool execution errors must NOT surface as protocol errors") + require.NotNil(t, res) + assert.True(t, res.IsError, "check_permissions with FGA disabled must return IsError=true") + require.NotEmpty(t, res.Content) + txt, ok := res.Content[0].(*mcp.TextContent) + require.True(t, ok, "error content should be text") + assert.Contains(t, txt.Text, "fine-grained authorization is not enabled", + "fail-closed FGA error message must surface to the MCP host") +} diff --git a/internal/integration_tests/mcp_test.go b/internal/integration_tests/mcp_test.go new file mode 100644 index 00000000..ab49b2cc --- /dev/null +++ b/internal/integration_tests/mcp_test.go @@ -0,0 +1,95 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/internal/grpcsrv" + authmcp "github.com/authorizerdev/authorizer/internal/mcp" + "github.com/authorizerdev/authorizer/internal/service" +) + +// TestMCPListAndCallMeta exercises the vertical slice end-to-end on the +// consolidated single-service design: boot a gRPC server, wrap it in the +// MCP server (which auto-discovers tools from proto annotations), connect a +// client via in-memory transports, then list_tools + call meta. +func TestMCPListAndCallMeta(t *testing.T) { + cfg := getTestConfig() + cfg.ClientID = "test-client" + + log := zerolog.New(zerolog.NewTestWriter(t)).With().Timestamp().Logger() + + svc, err := service.New(cfg, &service.Dependencies{Log: &log}) + require.NoError(t, err) + + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: &log, + Config: cfg, + ServiceProvider: svc, + }) + require.NoError(t, err) + + mcpSrv, err := authmcp.New(&log, grpcSrv.GRPCServer(), authmcp.Options{Name: "authorizer-test", Version: "v0"}) + require.NoError(t, err) + + // Wire client ↔ server via in-memory transports (no stdio). + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + cTransport, sTransport := mcp.NewInMemoryTransports() + serverSession, err := mcpSrv.MCPServer().Connect(ctx, sTransport, nil) + require.NoError(t, err) + defer serverSession.Close() + + client := mcp.NewClient(&mcp.Implementation{Name: "test", Version: "v0"}, nil) + clientSession, err := client.Connect(ctx, cTransport, nil) + require.NoError(t, err) + defer clientSession.Close() + + // tools/list — should include the proto-annotated MCP tools: + // meta, profile, check_permissions, list_permissions. The single + // `permissions` tool was replaced by the OpenFGA dual-API + // (CheckPermissions/ListPermissions) — tool names are + // snake_case(method), so "check_permissions"/"list_permissions". + // (Session was DROPPED from MCP exposure in the security pass; its + // response carries credentials that shouldn't land in an LLM + // transcript — audit finding C1.) + list, err := clientSession.ListTools(ctx, nil) + require.NoError(t, err) + gotNames := map[string]bool{} + for _, tool := range list.Tools { + gotNames[tool.Name] = true + } + for _, want := range []string{"meta", "profile", "check_permissions", "list_permissions"} { + require.True(t, gotNames[want], "expected MCP tool %q to be exposed; got %v", want, gotNames) + } + require.False(t, gotNames["permissions"], + "legacy `permissions` tool MUST NOT be exposed; it was replaced by check_permissions/list_permissions") + require.False(t, gotNames["session"], + "session tool MUST NOT be exposed via MCP (carries access_token/refresh_token/etc.)") + + // tools/call meta — should invoke AuthorizerService.Meta and return JSON + // wrapped in the per-RPC MetaResponse shape. + call, err := clientSession.CallTool(ctx, &mcp.CallToolParams{ + Name: "meta", + Arguments: map[string]any{}, + }) + require.NoError(t, err) + require.NotNil(t, call.StructuredContent) + + body, err := json.Marshal(call.StructuredContent) + require.NoError(t, err) + var got struct { + Meta struct { + ClientID string `json:"client_id"` + Version string `json:"version"` + } `json:"meta"` + } + require.NoError(t, json.Unmarshal(body, &got)) + require.Equal(t, "test-client", got.Meta.ClientID) + require.NotEmpty(t, got.Meta.Version) +} diff --git a/internal/integration_tests/rest_errors_test.go b/internal/integration_tests/rest_errors_test.go new file mode 100644 index 00000000..deee4ebd --- /dev/null +++ b/internal/integration_tests/rest_errors_test.go @@ -0,0 +1,205 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "io" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" + "github.com/authorizerdev/authorizer/internal/gateway" + "github.com/authorizerdev/authorizer/internal/graph/model" + "github.com/authorizerdev/authorizer/internal/grpcsrv" +) + +// bootRESTGateway boots the gRPC server + grpc-gateway over an httptest server, +// mirroring the production wiring (the gateway mounted under the gin router at +// /v1/*). It uses the fully-wired service from initTestSetup so auth-bearing +// methods (Profile, Logout, etc.) have real Token/Storage providers rather +// than nil stubs. Returns the base URL. +func bootRESTGateway(t *testing.T) string { + t.Helper() + cfg := getTestConfig() + cfg.ClientID = "test-client" + + s := initTestSetup(t, cfg) + + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: s.Logger, + Config: cfg, + ServiceProvider: s.ServiceProvider, + }) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + gw, cleanup, err := gateway.Handler(ctx, grpcSrv.GRPCServer()) + require.NoError(t, err) + t.Cleanup(cleanup) + + gin.SetMode(gin.TestMode) + r := gin.New() + r.Any("/v1/*path", gin.WrapH(gw)) + ts := httptest.NewServer(r) + t.Cleanup(ts.Close) + return ts.URL +} + +// decodeErrorEnvelope reads the standard REST error envelope. +type errorEnvelope struct { + Code string `json:"code"` + Message string `json:"message"` +} + +func readEnvelope(t *testing.T, resp *http.Response) errorEnvelope { + t.Helper() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + var env errorEnvelope + require.NoError(t, json.Unmarshal(body, &env), "body: %s", string(body)) + return env +} + +// TestRESTErrorEnvelopeAndStatusCodes verifies that business errors surface as +// proper HTTP status codes (not a blanket 500) and in the stable snake_case +// error envelope {"code": ..., "message": ...}. Exercises the service typed +// errors -> ErrorMap interceptor -> grpc-gateway error handler chain. +func TestRESTErrorEnvelopeAndStatusCodes(t *testing.T) { + base := bootRESTGateway(t) + + t.Run("signup missing email and phone -> 400 invalid_argument", func(t *testing.T) { + resp, err := http.Post(base+"/v1/signup", "application/json", + strings.NewReader(`{"password":"Test@123","confirm_password":"Test@123"}`)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "invalid_argument", env.Code) + require.Equal(t, "email or phone number is required", env.Message) + }) + + t.Run("profile without auth -> 401 unauthenticated", func(t *testing.T) { + resp, err := http.Get(base + "/v1/profile") + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusUnauthorized, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "unauthenticated", env.Code) + }) + + t.Run("validate_jwt_token bad token type -> 400 invalid_argument", func(t *testing.T) { + resp, err := http.Post(base+"/v1/validate_jwt_token", "application/json", + strings.NewReader(`{"token_type":"bogus","token":"x"}`)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "invalid_argument", env.Code) + require.Equal(t, "invalid token type", env.Message) + }) +} + +// TestErrorMessageConsistencyAcrossProtocols asserts that the SAME business +// error surfaces with the IDENTICAL message string over GraphQL, gRPC, and +// REST. All three transports delegate to the same internal/service method and +// surface its service.Error.Error() text verbatim — GraphQL as the resolver +// error, gRPC as the status message, REST as the envelope `message`. This test +// is the regression guard for that contract. +func TestErrorMessageConsistencyAcrossProtocols(t *testing.T) { + cfg := getTestConfig() + cfg.ClientID = "test-client" + s := initTestSetup(t, cfg) + + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: s.Logger, + Config: cfg, + ServiceProvider: s.ServiceProvider, + }) + require.NoError(t, err) + + // gRPC client over an in-process bufconn served by the same server. + lis := bufconn.Listen(1 << 20) + t.Cleanup(func() { _ = lis.Close() }) + go func() { _ = grpcSrv.GRPCServer().Serve(lis) }() + t.Cleanup(grpcSrv.GRPCServer().GracefulStop) + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + + // REST gateway over the same gRPC server. + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + gw, cleanup, err := gateway.Handler(ctx, grpcSrv.GRPCServer()) + require.NoError(t, err) + t.Cleanup(cleanup) + gin.SetMode(gin.TestMode) + r := gin.New() + r.Any("/v1/*path", gin.WrapH(gw)) + ts := httptest.NewServer(r) + t.Cleanup(ts.Close) + + // "email or phone number is required" — the first validation in SignUp, + // reached identically regardless of transport. + const wantMsg = "email or phone number is required" + + // GraphQL. + _, ctxGin := createContext(s) + _, gqlErr := s.GraphQLProvider.SignUp(ctxGin, &model.SignUpRequest{ + Password: "Test@123", + ConfirmPassword: "Test@123", + }) + require.Error(t, gqlErr) + require.Equal(t, wantMsg, gqlErr.Error(), "GraphQL error message") + + // gRPC. + _, grpcErr := authorizerv1.NewAuthorizerServiceClient(conn).Signup(context.Background(), + &authorizerv1.SignupRequest{Password: "Test@123", ConfirmPassword: "Test@123"}) + require.Error(t, grpcErr) + require.Equal(t, wantMsg, status.Convert(grpcErr).Message(), "gRPC status message") + + // REST. + resp, err := http.Post(ts.URL+"/v1/signup", "application/json", + strings.NewReader(`{"password":"Test@123","confirm_password":"Test@123"}`)) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, wantMsg, readEnvelope(t, resp).Message, "REST envelope message") +} + +// TestRESTLogoutIsPost asserts logout is mapped to POST (a mutating, audited +// operation must not be a safe GET). GET must not be routed to the handler. +func TestRESTLogoutIsPost(t *testing.T) { + base := bootRESTGateway(t) + + // GET is not a registered method for /v1/logout -> gateway returns 405. + getResp, err := http.Get(base + "/v1/logout") + require.NoError(t, err) + defer getResp.Body.Close() + require.Equal(t, http.StatusMethodNotAllowed, getResp.StatusCode) + + // POST reaches the handler; with no session it is unauthenticated (401), + // proving the route exists and errors flow through the envelope. + postResp, err := http.Post(base+"/v1/logout", "application/json", strings.NewReader(`{}`)) + require.NoError(t, err) + defer postResp.Body.Close() + require.Equal(t, http.StatusUnauthorized, postResp.StatusCode) + env := readEnvelope(t, postResp) + require.Equal(t, "unauthenticated", env.Code) +} diff --git a/internal/integration_tests/rest_meta_test.go b/internal/integration_tests/rest_meta_test.go new file mode 100644 index 00000000..85786d30 --- /dev/null +++ b/internal/integration_tests/rest_meta_test.go @@ -0,0 +1,71 @@ +package integration_tests + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/internal/gateway" + "github.com/authorizerdev/authorizer/internal/grpcsrv" + "github.com/authorizerdev/authorizer/internal/service" +) + +// TestRESTMeta exercises GET /v1/meta through the grpc-gateway. Validates +// that the gateway translates the REST call into an in-process gRPC +// invocation against AuthorizerService.Meta, then renders the response as +// JSON. The wrapped response shape (`{"meta": {...}}`) is intentional: +// every AuthorizerService RPC's response is a thin wrapper around the +// inner type so buf STANDARD's RPC_REQUEST_RESPONSE_UNIQUE lint is satisfied. +func TestRESTMeta(t *testing.T) { + cfg := getTestConfig() + cfg.ClientID = "test-client" + + log := zerolog.New(zerolog.NewTestWriter(t)).With().Timestamp().Logger() + + svc, err := service.New(cfg, &service.Dependencies{Log: &log}) + require.NoError(t, err) + + grpcSrv, err := grpcsrv.New(":0", &grpcsrv.Dependencies{ + Log: &log, + Config: cfg, + ServiceProvider: svc, + }) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + gw, cleanup, err := gateway.Handler(ctx, grpcSrv.GRPCServer()) + require.NoError(t, err) + t.Cleanup(cleanup) + + gin.SetMode(gin.TestMode) + r := gin.New() + r.Any("/v1/*path", gin.WrapH(gw)) + ts := httptest.NewServer(r) + t.Cleanup(ts.Close) + + resp, err := http.Get(ts.URL + "/v1/meta") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + var got struct { + Meta struct { + ClientID string `json:"client_id"` + Version string `json:"version"` + } `json:"meta"` + } + require.NoError(t, json.Unmarshal(body, &got)) + require.Equal(t, "test-client", got.Meta.ClientID) + require.NotEmpty(t, got.Meta.Version) +} diff --git a/internal/integration_tests/rest_openapi_test.go b/internal/integration_tests/rest_openapi_test.go new file mode 100644 index 00000000..862c7e62 --- /dev/null +++ b/internal/integration_tests/rest_openapi_test.go @@ -0,0 +1,46 @@ +package integration_tests + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/gen/openapi" +) + +// TestOpenAPIEndpointServesValidJSON verifies the /openapi.json route +// returns the embedded swagger spec, with a body that parses as JSON and +// declares the v1 services. Guards against two regressions: +// 1. Path-based reads of the spec file would fail when cwd is not the +// repo root (Docker, tests). The embed should make this path-free. +// 2. The merged swagger is non-empty and includes recognisable v1 routes. +func TestOpenAPIEndpointServesValidJSON(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.New() + r.GET("/openapi.json", func(c *gin.Context) { + c.Data(http.StatusOK, "application/json", openapi.Spec()) + }) + + ts := httptest.NewServer(r) + t.Cleanup(ts.Close) + + resp, err := http.Get(ts.URL + "/openapi.json") + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, "application/json", resp.Header.Get("Content-Type")) + + var doc map[string]any + require.NoError(t, json.NewDecoder(resp.Body).Decode(&doc)) + + // Sanity: swagger 2.0 doc with at least one path under /v1. + assert.Contains(t, doc, "swagger") + paths, ok := doc["paths"].(map[string]any) + require.True(t, ok, "openapi spec missing paths object") + assert.NotEmpty(t, paths, "openapi spec should declare at least one path") +} diff --git a/internal/integration_tests/rest_permissions_test.go b/internal/integration_tests/rest_permissions_test.go new file mode 100644 index 00000000..b6f07942 --- /dev/null +++ b/internal/integration_tests/rest_permissions_test.go @@ -0,0 +1,144 @@ +package integration_tests + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/authorizerdev/authorizer/internal/service" +) + +// TestRESTCheckPermissionsFailClosed mirrors the gRPC fail-closed contract over +// the grpc-gateway REST surface: POST /v1/check_permissions with no FGA engine +// configured returns the service.ErrFgaNotEnabled error. That plain error maps +// to codes.FailedPrecondition -> HTTP 400, rendered in the stable snake_case +// envelope {"code": "failed_precondition", "message": "..."}. +// +// The engine-nil guard is the first check in the service method, so even this +// unauthenticated request surfaces the FGA-disabled error rather than a 401. +func TestRESTCheckPermissionsFailClosed(t *testing.T) { + base := bootRESTGateway(t) + + resp, err := http.Post(base+"/v1/check_permissions", "application/json", + strings.NewReader(`{"checks":[{"relation":"can_view","object":"document:1"}]}`)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "failed_precondition", env.Code) + require.Equal(t, service.ErrFgaNotEnabled.Error(), env.Message) +} + +// TestRESTListPermissionsFailClosed is the ListPermissions counterpart: with no +// FGA engine, POST /v1/list_permissions fails closed with the same envelope. +func TestRESTListPermissionsFailClosed(t *testing.T) { + base := bootRESTGateway(t) + + resp, err := http.Post(base+"/v1/list_permissions", "application/json", + strings.NewReader(`{}`)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "failed_precondition", env.Code) + require.Equal(t, service.ErrFgaNotEnabled.Error(), env.Message) +} + +// TestRESTCheckPermissionsEmptyChecksRejected verifies the protovalidate +// min_items=1 constraint on CheckPermissionsRequest.checks is enforced over +// REST too: an empty checks array is rejected by the validate interceptor with +// codes.InvalidArgument -> HTTP 400, before the handler/engine guard runs. +func TestRESTCheckPermissionsEmptyChecksRejected(t *testing.T) { + base := bootRESTGateway(t) + + resp, err := http.Post(base+"/v1/check_permissions", "application/json", + strings.NewReader(`{"checks":[]}`)) + require.NoError(t, err) + defer resp.Body.Close() + + require.Equal(t, http.StatusBadRequest, resp.StatusCode) + env := readEnvelope(t, resp) + require.Equal(t, "invalid_argument", env.Code) +} + +// TestRESTPermissionsAreNotGet asserts the permission RPCs are mapped to POST +// (they evaluate FGA decisions and accept a request body). A GET on the path +// must not route to the handler — the gateway returns 405. +func TestRESTPermissionsAreNotGet(t *testing.T) { + base := bootRESTGateway(t) + + for _, path := range []string{"/v1/check_permissions", "/v1/list_permissions"} { + resp, err := http.Get(base + path) + require.NoError(t, err) + require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode, "GET %s should be 405", path) + _ = resp.Body.Close() + } +} + +// TestRESTGatewayForwardsAuthorizerHost is the regression test for the +// gateway's WithMetadata annotator. The in-process bufconn call carries +// ":authority=bufconn"; without forwarding the original request's resolved +// host as `x-authorizer-url` metadata, tokens minted over REST would carry +// iss=http://bufconn and JWT issuer validation would reject every token on +// any surface. The test signs up over REST with an explicit X-Authorizer-URL, +// asserts the minted access token's iss claim echoes it, then proves the +// token round-trips: GET /v1/profile with the same host header returns the +// authenticated user. +func TestRESTGatewayForwardsAuthorizerHost(t *testing.T) { + base := bootRESTGateway(t) + const hostURL = "http://auth.test.example" + + body := strings.NewReader(`{"email":"rest-host@test.dev","password":"Rest-Host-123!","confirm_password":"Rest-Host-123!"}`) + req, err := http.NewRequest(http.MethodPost, base+"/v1/signup", body) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Authorizer-URL", hostURL) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode) + + var signup struct { + Auth struct { + AccessToken string `json:"access_token"` + } `json:"auth"` + } + require.NoError(t, json.NewDecoder(resp.Body).Decode(&signup)) + require.NotEmpty(t, signup.Auth.AccessToken) + + // The iss claim must be the host the gateway forwarded — not the + // in-process bufconn authority. + parts := strings.Split(signup.Auth.AccessToken, ".") + require.Len(t, parts, 3) + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + require.NoError(t, err) + var claims struct { + Iss string `json:"iss"` + } + require.NoError(t, json.Unmarshal(payload, &claims)) + require.Equal(t, hostURL, claims.Iss, + "gateway must forward the resolved authorizer host; got iss=%q", claims.Iss) + + // Round-trip: the REST-minted token authenticates a REST identity call. + preq, err := http.NewRequest(http.MethodGet, base+"/v1/profile", nil) + require.NoError(t, err) + preq.Header.Set("Authorization", "Bearer "+signup.Auth.AccessToken) + preq.Header.Set("X-Authorizer-URL", hostURL) + presp, err := http.DefaultClient.Do(preq) + require.NoError(t, err) + defer presp.Body.Close() + require.Equal(t, http.StatusOK, presp.StatusCode) + var profile struct { + User struct { + Email string `json:"email"` + } `json:"user"` + } + require.NoError(t, json.NewDecoder(presp.Body).Decode(&profile)) + require.Equal(t, "rest-host@test.dev", profile.User.Email) +} diff --git a/internal/integration_tests/test_helper.go b/internal/integration_tests/test_helper.go index d154500b..15cdb8fc 100644 --- a/internal/integration_tests/test_helper.go +++ b/internal/integration_tests/test_helper.go @@ -24,6 +24,7 @@ import ( "github.com/authorizerdev/authorizer/internal/memory_store" "github.com/authorizerdev/authorizer/internal/oauth" "github.com/authorizerdev/authorizer/internal/rate_limit" + "github.com/authorizerdev/authorizer/internal/service" "github.com/authorizerdev/authorizer/internal/sms" "github.com/authorizerdev/authorizer/internal/storage" "github.com/authorizerdev/authorizer/internal/token" @@ -43,6 +44,10 @@ type testSetup struct { MemoryStoreProvider memory_store.Provider AuthenticatorProvider authenticators.Provider TokenProvider token.Provider + // ServiceProvider is the transport-agnostic service layer, exposed so the + // gRPC/REST transport tests can mount the same fully-wired service the + // GraphQL path uses. + ServiceProvider service.Provider } func createContext(s *testSetup) (*http.Request, context.Context) { @@ -190,6 +195,19 @@ func initTestSetup(t *testing.T, cfg *config.Config) *testSetup { StorageProvider: storageProvider, }) + // Transport-agnostic service layer for migrated public ops. + serviceProvider, err := service.New(cfg, &service.Dependencies{ + Log: &logger, + AuditProvider: auditProvider, + EmailProvider: emailProvider, + EventsProvider: eventsProvider, + MemoryStoreProvider: memoryStoreProvider, + SMSProvider: smsProvider, + StorageProvider: storageProvider, + TokenProvider: tokenProvider, + }) + require.NoError(t, err) + // Create dependencies struct gqlDeps := &graphql.Dependencies{ Log: &logger, @@ -201,6 +219,7 @@ func initTestSetup(t *testing.T, cfg *config.Config) *testSetup { SMSProvider: smsProvider, StorageProvider: storageProvider, TokenProvider: tokenProvider, + ServiceProvider: serviceProvider, } // Create dependencies struct @@ -216,6 +235,7 @@ func initTestSetup(t *testing.T, cfg *config.Config) *testSetup { TokenProvider: tokenProvider, RateLimitProvider: rateLimitProvider, OAuthProvider: oauthProvider, + ServiceProvider: serviceProvider, } // Create GraphQL provider @@ -261,6 +281,7 @@ func initTestSetup(t *testing.T, cfg *config.Config) *testSetup { MemoryStoreProvider: memoryStoreProvider, AuthenticatorProvider: authProvider, TokenProvider: tokenProvider, + ServiceProvider: serviceProvider, } } diff --git a/internal/mcp/scanner.go b/internal/mcp/scanner.go new file mode 100644 index 00000000..4b461d33 --- /dev/null +++ b/internal/mcp/scanner.go @@ -0,0 +1,113 @@ +// Package mcp exposes a subset of Authorizer's gRPC methods as MCP tools. +// Which methods are exposed is declared at the proto layer via the custom +// option `authorizer.common.v1.mcp_tool` — the scanner reads it at startup +// to build the tool registry. No service-by-service hand-registration. +package mcp + +import ( + "fmt" + "strings" + + "google.golang.org/grpc" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + + commonv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/common/v1" +) + +// ToolBinding is one MCP-exposed RPC: a tool name, plus enough metadata to +// dispatch a JSON-arg invocation back to the gRPC server. +type ToolBinding struct { + // Name surfaced to MCP clients (e.g. "get_meta"). Defaults to + // snake_case(method) unless the proto annotation overrides it. + Name string + // Description from the RPC's leading comment, surfaced to the MCP host. + Description string + // Destructive hints to the MCP host that user confirmation is warranted. + Destructive bool + + // FullMethod is the gRPC method name in `/pkg.Service/Method` form. + // Used directly with grpc.ClientConn.Invoke. + FullMethod string + // InputDescriptor / OutputDescriptor are the proto message descriptors + // for request/response. Used by the dispatcher to construct dynamic + // proto.Message instances for JSON unmarshalling/marshalling. + InputDescriptor protoreflect.MessageDescriptor + OutputDescriptor protoreflect.MessageDescriptor +} + +// Scan walks the supplied gRPC server's registered services and returns the +// set of methods marked `(authorizer.common.v1.mcp_tool).exposed = true`. +// Methods that aren't exposed (the default) are silently skipped. +func Scan(srv *grpc.Server) ([]ToolBinding, error) { + var bindings []ToolBinding + for svcName := range srv.GetServiceInfo() { + // Look up the proto descriptor for this service by full name. + desc, err := protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(svcName)) + if err != nil { + // Not all gRPC services come from compiled proto (e.g. the + // gRPC health checking and reflection services). Skip silently. + continue + } + svcDesc, ok := desc.(protoreflect.ServiceDescriptor) + if !ok { + continue + } + + methods := svcDesc.Methods() + for i := 0; i < methods.Len(); i++ { + m := methods.Get(i) + tool := mcpToolFromMethod(m) + if tool == nil || !tool.Exposed { + continue + } + + name := tool.ToolName + if name == "" { + name = camelToSnake(string(m.Name())) + } + + bindings = append(bindings, ToolBinding{ + Name: name, + Description: strings.TrimSpace(string(m.ParentFile().SourceLocations().ByDescriptor(m).LeadingComments)), + Destructive: tool.Destructive, + FullMethod: fmt.Sprintf("/%s/%s", svcName, m.Name()), + InputDescriptor: m.Input(), + OutputDescriptor: m.Output(), + }) + } + } + return bindings, nil +} + +// mcpToolFromMethod reads the (authorizer.common.v1.mcp_tool) option off a +// method descriptor. Returns nil when the option is absent or unset. +func mcpToolFromMethod(m protoreflect.MethodDescriptor) *commonv1.McpTool { + opts := m.Options() + if opts == nil { + return nil + } + t, ok := proto.GetExtension(opts, commonv1.E_McpTool).(*commonv1.McpTool) + if !ok || t == nil { + return nil + } + return t +} + +// camelToSnake converts MixedCase / camelCase to snake_case. ASCII only; +// proto method names never contain non-ASCII. +func camelToSnake(s string) string { + var b strings.Builder + for i, r := range s { + if i > 0 && r >= 'A' && r <= 'Z' { + b.WriteByte('_') + } + if r >= 'A' && r <= 'Z' { + b.WriteRune(r + ('a' - 'A')) + } else { + b.WriteRune(r) + } + } + return b.String() +} diff --git a/internal/mcp/schema.go b/internal/mcp/schema.go new file mode 100644 index 00000000..7246f7c2 --- /dev/null +++ b/internal/mcp/schema.go @@ -0,0 +1,87 @@ +package mcp + +import ( + "google.golang.org/protobuf/reflect/protoreflect" +) + +// jsonSchema is a tiny JSON-Schema subset — enough to describe the input of +// a typical Authorizer RPC. We don't bring in a full schema library because +// the MCP host only needs property names, types, and descriptions for tool +// discovery. +type jsonSchema struct { + Type string `json:"type"` + Properties map[string]jsonSchema `json:"properties,omitempty"` + Items *jsonSchema `json:"items,omitempty"` + Description string `json:"description,omitempty"` + Required []string `json:"required,omitempty"` +} + +// schemaForMessage derives a JSON Schema (object form) for a proto message +// descriptor. Field naming uses the proto field name (snake_case), matching +// the gateway's UseProtoNames=true configuration. +func schemaForMessage(md protoreflect.MessageDescriptor) jsonSchema { + return schemaForMessageWithVisited(md, map[protoreflect.FullName]struct{}{}) +} + +// schemaForMessageWithVisited recurses into nested message fields while +// guarding against cycles. The descriptor full-name is the visit key — +// well-known types like google.protobuf.Value reference themselves via +// repeated-Value lists, which would stack-overflow without this. +// +// On a re-visit we emit an opaque `object` rather than the full schema, +// which is the most honest thing to tell an MCP host about a self-recursive +// type (it can pass any JSON object; the server validates at the proto +// layer via protovalidate). +func schemaForMessageWithVisited(md protoreflect.MessageDescriptor, visited map[protoreflect.FullName]struct{}) jsonSchema { + if _, seen := visited[md.FullName()]; seen { + return jsonSchema{Type: "object"} + } + visited[md.FullName()] = struct{}{} + defer delete(visited, md.FullName()) + + root := jsonSchema{ + Type: "object", + Properties: map[string]jsonSchema{}, + } + fields := md.Fields() + for i := 0; i < fields.Len(); i++ { + f := fields.Get(i) + root.Properties[string(f.Name())] = schemaForField(f, visited) + } + return root +} + +func schemaForField(f protoreflect.FieldDescriptor, visited map[protoreflect.FullName]struct{}) jsonSchema { + // repeated → JSON array + if f.IsList() { + item := schemaForKind(f, visited) + return jsonSchema{Type: "array", Items: &item} + } + if f.IsMap() { + return jsonSchema{Type: "object"} + } + return schemaForKind(f, visited) +} + +func schemaForKind(f protoreflect.FieldDescriptor, visited map[protoreflect.FullName]struct{}) jsonSchema { + switch f.Kind() { + case protoreflect.BoolKind: + return jsonSchema{Type: "boolean"} + case protoreflect.Int32Kind, protoreflect.Int64Kind, + protoreflect.Uint32Kind, protoreflect.Uint64Kind, + protoreflect.Sint32Kind, protoreflect.Sint64Kind, + protoreflect.Fixed32Kind, protoreflect.Fixed64Kind, + protoreflect.Sfixed32Kind, protoreflect.Sfixed64Kind: + return jsonSchema{Type: "integer"} + case protoreflect.FloatKind, protoreflect.DoubleKind: + return jsonSchema{Type: "number"} + case protoreflect.StringKind, protoreflect.BytesKind: + return jsonSchema{Type: "string"} + case protoreflect.EnumKind: + return jsonSchema{Type: "string"} + case protoreflect.MessageKind, protoreflect.GroupKind: + return schemaForMessageWithVisited(f.Message(), visited) + default: + return jsonSchema{Type: "string"} + } +} diff --git a/internal/mcp/schema_test.go b/internal/mcp/schema_test.go new file mode 100644 index 00000000..fafff1a1 --- /dev/null +++ b/internal/mcp/schema_test.go @@ -0,0 +1,112 @@ +package mcp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/reflect/protoreflect" + + authorizerv1 "github.com/authorizerdev/authorizer/gen/go/authorizer/v1" +) + +// TestSchemaForMessage_FlatScalars covers the most common case: a request +// message with only scalar fields. SignupRequest is a good representative — +// string / repeated string / bool / message-typed (AppData). +func TestSchemaForMessage_FlatScalars(t *testing.T) { + md := (&authorizerv1.SignupRequest{}).ProtoReflect().Descriptor() + s := schemaForMessage(md) + + assert.Equal(t, "object", s.Type) + require.NotNil(t, s.Properties) + + assert.Equal(t, "string", s.Properties["email"].Type) + assert.Equal(t, "string", s.Properties["password"].Type) + assert.Equal(t, "boolean", s.Properties["is_multi_factor_auth_enabled"].Type) + + // repeated string → array of strings + roles := s.Properties["roles"] + require.Equal(t, "array", roles.Type) + require.NotNil(t, roles.Items) + assert.Equal(t, "string", roles.Items.Type) + + // Nested message field (AppData) — recurses into its sub-schema. + app := s.Properties["app_data"] + assert.Equal(t, "object", app.Type) +} + +// TestSchemaForMessage_EmptyRequest — MetaRequest has no fields. +func TestSchemaForMessage_EmptyRequest(t *testing.T) { + md := (&authorizerv1.MetaRequest{}).ProtoReflect().Descriptor() + s := schemaForMessage(md) + assert.Equal(t, "object", s.Type) + assert.Empty(t, s.Properties) +} + +// TestSchemaForMessage_CycleSafe — google.protobuf.Value references itself +// via repeated Value (ListValue.values). Before the cycle-guard fix, exposing +// any tool whose request includes a Struct or Value field would stack-overflow +// at boot. The visited-set short-circuits and emits an opaque `object`. +func TestSchemaForMessage_CycleSafe(t *testing.T) { + // AppData wraps google.protobuf.Struct, which contains a + // map, where Value can hold a ListValue of more Values. + // That's the exact recursion that would stack-overflow without the guard. + app := (&authorizerv1.SignupRequest{}).ProtoReflect().Descriptor().Fields().ByName("app_data") + require.NotNil(t, app) + + schema := schemaForField(app, map[protoreflect.FullName]struct{}{}) + // Doesn't panic / overflow. The deeply-nested Value type collapses to + // an opaque object once the cycle is detected. + assert.Equal(t, "object", schema.Type) +} + +// TestSchemaForMessage_ScalarOnly walks a request that's purely scalars +// (no nested message). Profile takes no arguments at all; Session takes +// a few list-of-string + nested PermissionInput. +func TestSchemaForMessage_AllScalarKinds(t *testing.T) { + md := (&authorizerv1.ValidateJwtTokenRequest{}).ProtoReflect().Descriptor() + s := schemaForMessage(md) + assert.Equal(t, "string", s.Properties["token_type"].Type) + assert.Equal(t, "string", s.Properties["token"].Type) + assert.Equal(t, "array", s.Properties["roles"].Type) +} + +// TestSchemaForMessage_CheckPermissionsNesting proves the generator handles +// the two-level nesting introduced by the OpenFGA dual-API: +// CheckPermissionsRequest.checks[] is a repeated PermissionCheckInput, and +// each PermissionCheckInput.contextual_tuples[] is a repeated FgaTupleInput. +// We assert the schema walks array → object → array → object down to the +// leaf scalars, so an MCP host sees the full input shape (not an opaque +// object) for a batch permission check with contextual tuples. +func TestSchemaForMessage_CheckPermissionsNesting(t *testing.T) { + md := (&authorizerv1.CheckPermissionsRequest{}).ProtoReflect().Descriptor() + s := schemaForMessage(md) + + assert.Equal(t, "object", s.Type) + require.NotNil(t, s.Properties) + + // Optional explicit subject — a plain scalar alongside the nested list. + assert.Equal(t, "string", s.Properties["user"].Type) + + // checks → array of PermissionCheckInput objects. + checks := s.Properties["checks"] + require.Equal(t, "array", checks.Type) + require.NotNil(t, checks.Items, "checks must describe its element schema") + assert.Equal(t, "object", checks.Items.Type) + + // PermissionCheckInput scalar fields. + assert.Equal(t, "string", checks.Items.Properties["relation"].Type) + assert.Equal(t, "string", checks.Items.Properties["object"].Type) + + // contextual_tuples → array of FgaTupleInput objects (the second level + // of nesting; this is what regressed without proper recursion). + tuples := checks.Items.Properties["contextual_tuples"] + require.Equal(t, "array", tuples.Type) + require.NotNil(t, tuples.Items, "contextual_tuples must describe its element schema") + assert.Equal(t, "object", tuples.Items.Type) + + // FgaTupleInput leaf scalars — proves we reached the bottom of the tree. + assert.Equal(t, "string", tuples.Items.Properties["user"].Type) + assert.Equal(t, "string", tuples.Items.Properties["relation"].Type) + assert.Equal(t, "string", tuples.Items.Properties["object"].Type) +} diff --git a/internal/mcp/server.go b/internal/mcp/server.go new file mode 100644 index 00000000..ac6a33dd --- /dev/null +++ b/internal/mcp/server.go @@ -0,0 +1,234 @@ +// Package mcp serves a curated subset of Authorizer's gRPC methods to +// LLM clients via the Model Context Protocol. Stdio is the ONLY supported +// transport — see the deliberate design note on Server below. +package mcp + +import ( + "context" + "encoding/json" + "fmt" + "net" + "strings" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/dynamicpb" +) + +const bufSize = 1 << 20 + +// Server wraps an MCP server that bridges to an in-process gRPC server. +// +// Design constraint: stdio is the ONLY supported transport. The MCP server +// has no auth/rate-limit/audit interceptors of its own — it relies entirely +// on the OS-level trust boundary of the subprocess (Claude Code spawns +// `authorizer mcp` as a child; only that process can write to its stdin). +// Exposing the MCP server over TCP / HTTP / SSE would invalidate that +// assumption and is intentionally NOT implementable: there is no RunHTTP / +// RunTCP / RunSSE method, and adding one without first implementing an +// auth layer is a security regression. The stdio-only contract is also +// enforced by TestServer_StdioOnly. +type Server struct { + log *zerolog.Logger + mcpSrv *mcp.Server + gwConn *grpc.ClientConn + lis *bufconn.Listener + grpcSrv *grpc.Server + + // bearer is the value of the Authorization header stamped on every + // outgoing gRPC call. Set via Options.Bearer at construction time + // (the cmd/mcp.go subcommand exposes --mcp-bearer). When empty, calls + // flow without auth — fine for public methods like Meta, but anything + // requiring identity (Profile, CheckPermissions, ...) will see an empty + // caller and return whatever its handler does in that case. + bearer string + // authorizerURL is stamped as `x-authorizer-url` metadata on every + // outgoing gRPC call. JWT issuer validation compares a token's `iss` + // against the resolved host; the in-process bufconn call would resolve + // to "http://bufconn", so without this every bearer token minted by the + // real server would be rejected. Set it to the public URL of the + // Authorizer instance that issued the bearer token. + authorizerURL string +} + +// Options configures the MCP server. +type Options struct { + // Name is the MCP server's reported implementation name. + Name string + // Version is the MCP server's reported implementation version. + Version string + // Bearer, when set, is propagated as `Authorization: Bearer ` + // metadata on every gRPC dispatch. This is how MCP-side identity + // reaches the gRPC handlers (security audit H1). The bearer should be + // a token issued for the user the MCP host is acting on behalf of. + Bearer string + // AuthorizerURL, when set, is propagated as `x-authorizer-url` metadata + // on every gRPC dispatch so JWT issuer validation resolves the host the + // bearer token was minted by (not the in-process "bufconn" authority). + // Required for identity-bearing tools when Bearer is set. + AuthorizerURL string +} + +// New builds an MCP server that exposes every gRPC method on `grpcSrv` +// whose proto annotation has `(authorizer.common.v1.mcp_tool).exposed = true`. +// The gRPC server is served over an in-process bufconn — same pattern as +// the REST gateway — so MCP tool invocations become local method calls with +// no extra network hop. +func New(log *zerolog.Logger, grpcSrv *grpc.Server, opts Options) (*Server, error) { + bindings, err := Scan(grpcSrv) + if err != nil { + return nil, fmt.Errorf("mcp: scan tools: %w", err) + } + log.Info(). + Int("tools", len(bindings)). + Bool("authenticated", opts.Bearer != ""). + Msg("MCP: discovered tools from proto annotations") + + // Same bufconn dance as the REST gateway. + lis := bufconn.Listen(bufSize) + go func() { _ = grpcSrv.Serve(lis) }() + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { return lis.Dial() }), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + _ = lis.Close() + return nil, fmt.Errorf("mcp: dial in-process grpc: %w", err) + } + + mcpSrv := mcp.NewServer(&mcp.Implementation{ + Name: opts.Name, + Version: opts.Version, + }, nil) + + s := &Server{ + log: log, + mcpSrv: mcpSrv, + gwConn: conn, + lis: lis, + grpcSrv: grpcSrv, + bearer: opts.Bearer, + authorizerURL: opts.AuthorizerURL, + } + for _, b := range bindings { + s.registerTool(b) + } + return s, nil +} + +// MCPServer exposes the underlying *mcp.Server. Used by tests to drive the +// server with an in-memory transport pair. +func (s *Server) MCPServer() *mcp.Server { return s.mcpSrv } + +// RunStdio serves MCP over stdio (the default Claude Code transport). Blocks +// until ctx is cancelled or the client disconnects. +// +// This is the only `Run*` method on the Server. See the type comment for why +// adding a non-stdio transport is intentionally a code-level non-feature. +func (s *Server) RunStdio(ctx context.Context) error { + defer s.cleanup() + return s.mcpSrv.Run(ctx, &mcp.StdioTransport{}) +} + +func (s *Server) cleanup() { + _ = s.gwConn.Close() + _ = s.lis.Close() +} + +// stampAuth attaches the configured bearer and authorizer URL to the +// outgoing gRPC call. A no-op when neither is set. This is the bridge that +// lets gRPC handlers see "who is calling" (security audit H1) and which +// host minted the token (issuer validation) when invoked from MCP. +func (s *Server) stampAuth(ctx context.Context) context.Context { + if s.bearer != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+s.bearer) + } + if s.authorizerURL != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-authorizer-url", s.authorizerURL) + } + return ctx +} + +// registerTool wires one ToolBinding into the MCP server. The handler: +// 1. Constructs a fresh proto.Message of the right type via dynamicpb +// 2. Unmarshals JSON args into it +// 3. Invokes the gRPC method via grpc.ClientConn.Invoke (with bearer) +// 4. Marshals the response back to JSON for the MCP client +func (s *Server) registerTool(b ToolBinding) { + schema := schemaForMessage(b.InputDescriptor) + tool := &mcp.Tool{ + Name: b.Name, + Description: b.Description, + InputSchema: schema, + } + if b.Destructive { + // MCP clients show a destructive-action confirmation when this is set. + tool.Annotations = &mcp.ToolAnnotations{DestructiveHint: ptrTrue()} + } + + s.mcpSrv.AddTool(tool, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Build a dynamic proto.Message for the request, then unmarshal JSON. + reqMsg := dynamicpb.NewMessage(b.InputDescriptor) + if len(req.Params.Arguments) > 0 && !isJSONNull(req.Params.Arguments) { + if err := (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(req.Params.Arguments, reqMsg); err != nil { + // Argument decode failures surface as tool errors (not + // protocol errors) so the LLM gets actionable text. + return errorResult("invalid arguments: " + err.Error()), nil + } + } + + respMsg := dynamicpb.NewMessage(b.OutputDescriptor) + if err := s.gwConn.Invoke(s.stampAuth(ctx), b.FullMethod, reqMsg, respMsg); err != nil { + s.log.Debug().Err(err).Str("tool", b.Name).Str("method", b.FullMethod).Msg("MCP tool invocation failed") + // gRPC errors (Unimplemented, PermissionDenied, NotFound, ...) + // become CallToolResult{IsError: true} with the gRPC status + // message as the content. The MCP host shows this to the LLM + // in a way that lets it react / try a different tool, rather + // than a low-level JSON-RPC failure that would just abort. + return errorResult(err.Error()), nil + } + + respJSON, err := (protojson.MarshalOptions{UseProtoNames: true, EmitUnpopulated: true}).Marshal(respMsg) + if err != nil { + return errorResult("encode response: " + err.Error()), nil + } + // Surface as both Content (text-shaped) and StructuredContent so MCP + // clients that prefer either get something they can consume. + var structured any + _ = json.Unmarshal(respJSON, &structured) + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: string(respJSON)}}, + StructuredContent: structured, + }, nil + }) +} + +func ptrTrue() *bool { v := true; return &v } + +// errorResult wraps a message as a CallToolResult with IsError set. This is +// the MCP-spec way to tell the host that the tool *ran* but produced a +// recoverable error (vs the JSON-RPC-level error path which signals a +// protocol/transport failure). +func errorResult(msg string) *mcp.CallToolResult { + return &mcp.CallToolResult{ + IsError: true, + Content: []mcp.Content{&mcp.TextContent{Text: msg}}, + } +} + +// isJSONNull returns true when the raw JSON encodes a literal `null`, with +// any surrounding whitespace tolerated. +func isJSONNull(raw json.RawMessage) bool { + s := strings.TrimSpace(string(raw)) + return s == "null" +} + +// compile-time assertion that ToolBinding messages descriptors implement what we need. +var _ proto.Message = (*dynamicpb.Message)(nil) diff --git a/internal/mcp/stamp_test.go b/internal/mcp/stamp_test.go new file mode 100644 index 00000000..f28932fe --- /dev/null +++ b/internal/mcp/stamp_test.go @@ -0,0 +1,41 @@ +package mcp + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +// TestStampAuth covers the per-dispatch metadata bridge: the configured +// bearer must surface as `authorization` and the configured authorizer URL +// as `x-authorizer-url` (JWT issuer validation resolves the host from it — +// without it the in-process bufconn authority would reject every token). +func TestStampAuth(t *testing.T) { + t.Run("no bearer, no url is a no-op", func(t *testing.T) { + s := &Server{} + ctx := s.stampAuth(context.Background()) + _, ok := metadata.FromOutgoingContext(ctx) + assert.False(t, ok) + }) + + t.Run("bearer and url are both stamped", func(t *testing.T) { + s := &Server{bearer: "tok-123", authorizerURL: "https://auth.example.com"} + ctx := s.stampAuth(context.Background()) + md, ok := metadata.FromOutgoingContext(ctx) + require.True(t, ok) + assert.Equal(t, []string{"Bearer tok-123"}, md.Get("authorization")) + assert.Equal(t, []string{"https://auth.example.com"}, md.Get("x-authorizer-url")) + }) + + t.Run("bearer without url stamps only authorization", func(t *testing.T) { + s := &Server{bearer: "tok-123"} + ctx := s.stampAuth(context.Background()) + md, ok := metadata.FromOutgoingContext(ctx) + require.True(t, ok) + assert.Equal(t, []string{"Bearer tok-123"}, md.Get("authorization")) + assert.Empty(t, md.Get("x-authorizer-url")) + }) +} diff --git a/internal/mcp/transport_test.go b/internal/mcp/transport_test.go new file mode 100644 index 00000000..dd1d6fff --- /dev/null +++ b/internal/mcp/transport_test.go @@ -0,0 +1,42 @@ +package mcp + +import ( + "reflect" + "strings" + "testing" +) + +// TestServer_StdioOnly is a guard against accidentally adding a non-stdio +// transport to the MCP server. Stdio is the only supported transport — the +// security model relies on the OS-level trust boundary of the subprocess +// (Claude Code spawns `authorizer mcp` as a child; only that process can +// write to its stdin). Exposing MCP over TCP/HTTP/SSE without an auth +// interceptor would be a security regression, so this test fails the build +// if anyone adds RunHTTP / RunTCP / RunSSE / Listen* / Serve* etc. +// +// To deliberately add a new transport: implement an auth+rate-limit +// interceptor for MCP first, then update this test's allow-list. +func TestServer_StdioOnly(t *testing.T) { + allowed := map[string]struct{}{ + "RunStdio": {}, + "MCPServer": {}, // test accessor — not a transport + } + t.Logf("MCP Server exported methods allow-list: %v (anything outside this set indicates a new transport)", allowed) + + st := reflect.TypeOf((*Server)(nil)) + for i := 0; i < st.NumMethod(); i++ { + name := st.Method(i).Name + if _, ok := allowed[name]; ok { + continue + } + // Heuristic: any method whose name suggests serving / running / + // listening over a different transport is a red flag. + lower := strings.ToLower(name) + for _, banned := range []string{"http", "tcp", "sse", "websocket", "listen", "serve", "run"} { + if strings.Contains(lower, banned) { + t.Errorf("disallowed transport method %q on *Server: stdio is the only supported MCP transport. "+ + "Adding a network transport requires an MCP-side auth interceptor first; see Server type comment.", name) + } + } + } +} diff --git a/internal/parsers/url.go b/internal/parsers/url.go index ddbfb0d8..24b29c70 100644 --- a/internal/parsers/url.go +++ b/internal/parsers/url.go @@ -1,17 +1,26 @@ package parsers import ( + "net/http" "net/url" "strings" "github.com/gin-gonic/gin" ) -// GetHost returns the authorizer host URL from the request context. -// Priority: X-Authorizer-URL header, then scheme (X-Forwarded-Proto) + host (X-Forwarded-Host or Request.Host). -// Headers are validated to prevent host header injection attacks. +// GetHost returns the authorizer host URL from the gin request context. +// Thin shim over GetHostFromRequest so non-gin transports (gRPC, plain HTTP) +// can reuse the same host-derivation logic. func GetHost(c *gin.Context) string { - authorizerURL := strings.TrimSpace(c.Request.Header.Get("X-Authorizer-URL")) + return GetHostFromRequest(c.Request) +} + +// GetHostFromRequest returns the authorizer host URL from a raw *http.Request. +// Priority: X-Authorizer-URL header, then scheme (X-Forwarded-Proto) + host +// (X-Forwarded-Host or Request.Host). Headers are validated to prevent host +// header injection attacks. +func GetHostFromRequest(r *http.Request) string { + authorizerURL := strings.TrimSpace(r.Header.Get("X-Authorizer-URL")) if authorizerURL != "" { if sanitized := sanitizeAuthorizerURL(authorizerURL); sanitized != "" { return sanitized @@ -19,13 +28,13 @@ func GetHost(c *gin.Context) string { // Invalid header value — fall through to standard host detection } - scheme := c.Request.Header.Get("X-Forwarded-Proto") + scheme := r.Header.Get("X-Forwarded-Proto") if scheme != "https" { scheme = "http" } - host := sanitizeHost(c.Request.Header.Get("X-Forwarded-Host")) + host := sanitizeHost(r.Header.Get("X-Forwarded-Host")) if host == "" { - host = sanitizeHost(c.Request.Host) + host = sanitizeHost(r.Host) } if host == "" { host = "localhost" @@ -131,6 +140,10 @@ func GetDomainName(uri string) string { // GetAppURL to get /app url if not configured by user func GetAppURL(gc *gin.Context) string { - envAppURL := GetHost(gc) + "/app" - return envAppURL + return GetAppURLFromRequest(gc.Request) +} + +// GetAppURLFromRequest is the transport-agnostic form of GetAppURL. +func GetAppURLFromRequest(r *http.Request) string { + return GetHostFromRequest(r) + "/app" } diff --git a/internal/parsers/url_test.go b/internal/parsers/url_test.go index ca71fb48..5dffd83a 100644 --- a/internal/parsers/url_test.go +++ b/internal/parsers/url_test.go @@ -1,6 +1,7 @@ package parsers import ( + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -60,3 +61,71 @@ func TestSanitizeHost(t *testing.T) { }) } } + +func TestGetHostFromRequest(t *testing.T) { + tests := []struct { + name string + headers map[string]string + host string + want string + }{ + { + name: "X-Authorizer-URL takes priority", + headers: map[string]string{ + "X-Authorizer-URL": "https://auth.example.com", + "X-Forwarded-Proto": "http", + "X-Forwarded-Host": "ignored.example.com", + }, + host: "request.example.com", + want: "https://auth.example.com", + }, + { + name: "falls back to X-Forwarded-Proto + X-Forwarded-Host", + headers: map[string]string{"X-Forwarded-Proto": "https", "X-Forwarded-Host": "edge.example.com"}, + host: "internal.example.com", + want: "https://edge.example.com", + }, + { + name: "ignores invalid X-Authorizer-URL", + headers: map[string]string{ + "X-Authorizer-URL": "user:pass@evil.example.com", + "X-Forwarded-Proto": "https", + "X-Forwarded-Host": "edge.example.com", + }, + host: "ignored", + want: "https://edge.example.com", + }, + { + name: "falls back to Request.Host", + headers: map[string]string{}, + host: "auth.example.com", + want: "http://auth.example.com", + }, + { + name: "defaults to localhost when nothing is set", + headers: map[string]string{}, + host: "", + want: "http://localhost", + }, + { + name: "rejects spoofed X-Forwarded-Host with path injection", + headers: map[string]string{"X-Forwarded-Host": "evil.example.com/path"}, + host: "auth.example.com", + want: "http://auth.example.com", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &http.Request{Host: tt.host, Header: http.Header{}} + for k, v := range tt.headers { + r.Header.Set(k, v) + } + assert.Equal(t, tt.want, GetHostFromRequest(r)) + }) + } +} + +func TestGetAppURLFromRequest(t *testing.T) { + r := &http.Request{Host: "auth.example.com", Header: http.Header{}} + assert.Equal(t, "http://auth.example.com/app", GetAppURLFromRequest(r)) +} diff --git a/internal/refs/bool.go b/internal/refs/bool.go index 4739eec1..20488257 100644 --- a/internal/refs/bool.go +++ b/internal/refs/bool.go @@ -11,4 +11,4 @@ func BoolValue(r *bool) bool { return false } return *r -} \ No newline at end of file +} diff --git a/internal/refs/int.go b/internal/refs/int.go index 974ef2a2..b3244276 100644 --- a/internal/refs/int.go +++ b/internal/refs/int.go @@ -11,4 +11,4 @@ func Int64Value(r *int64) int64 { return 0 } return *r -} \ No newline at end of file +} diff --git a/internal/refs/string.go b/internal/refs/string.go index 4447caba..b5ce84ff 100644 --- a/internal/refs/string.go +++ b/internal/refs/string.go @@ -15,5 +15,3 @@ func StringValue(r *string, defaultValue ...string) string { } return "" } - - diff --git a/internal/server/http_routes.go b/internal/server/http_routes.go index f619092b..29a862cf 100644 --- a/internal/server/http_routes.go +++ b/internal/server/http_routes.go @@ -3,10 +3,13 @@ package server import ( "encoding/json" "html/template" + "net/http" "path" "strings" "github.com/gin-gonic/gin" + + "github.com/authorizerdev/authorizer/gen/openapi" ) // spaBuildCacheMiddleware sets cache headers for SPA build assets: @@ -73,6 +76,27 @@ func (s *server) NewRouter() *gin.Engine { router.POST("/oauth/revoke", s.Dependencies.HTTPProvider.RevokeRefreshTokenHandler()) router.POST("/oauth/introspect", s.Dependencies.HTTPProvider.IntrospectHandler()) + // gRPC-gateway REST surface at /v1/*. Mounted only when the gRPC + // server is configured. Shares all gin middleware (CORS, security + // headers, rate limit, logging) automatically since the route group + // inherits them from `router.Use(...)` above. + if s.gatewayHandler != nil { + // The gateway's routes are registered with their full /v1/... path + // (driven by google.api.http annotations). Mount it as a catch-all + // under /v1 so gin matches the prefix and hands the full request + // path to grpc-gateway untouched. + gw := gin.WrapH(s.gatewayHandler) + router.Any("/v1/*path", gw) + + // OpenAPI spec — generated alongside the gRPC stubs by buf and + // embedded into the binary (so it works regardless of cwd: tests, + // containers, etc.). Path is intentionally separate from the + // gateway mux so it doesn't fight a /v1/openapi.json gateway route. + router.GET("/openapi.json", func(c *gin.Context) { + c.Data(http.StatusOK, "application/json", openapi.Spec()) + }) + } + // Set up template functions for JSON encoding. // Escape and