├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dependabot.yml ├── release.yml └── workflows │ ├── ci.yaml │ └── pr-title.yaml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── README.md ├── SECURITY.md ├── authn.go ├── authn_test.go ├── buf.gen.yaml ├── buf.yaml ├── examples_test.go ├── go.mod ├── go.sum └── internal ├── gen └── authn │ └── ping │ └── v1 │ ├── ping.pb.go │ └── pingv1connect │ └── ping.connect.go └── proto └── authn └── ping └── v1 └── ping.proto /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | conduct@buf.build. All complaints will be reviewed and investigated promptly 64 | and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | 134 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | We'd love your help making this package better! 5 | 6 | If you'd like to add new exported APIs, please [open an issue][open-issue] 7 | describing your proposal — discussing API changes ahead of time makes 8 | pull request review much smoother. In your issue, pull request, and any other 9 | communications, please remember to treat your fellow contributors with 10 | respect! 11 | 12 | Note that for a contribution to be accepted, you must sign off on all commits 13 | in order to affirm that they comply with the [Developer Certificate of Origin][dco]. 14 | Make sure to configure `git` with the same name and E-Mail as your GitHub account, 15 | and run `git commit` with the `-s` flag to sign. If necessary, a bot will remind 16 | you to sign your commits when you open your pull request, and provide helpful tips. 17 | 18 | ## Setup 19 | 20 | [Fork][fork], then clone the repository: 21 | 22 | ``` 23 | mkdir -p $GOPATH/src/connectrpc.com 24 | cd $GOPATH/src/connectrpc.com 25 | git clone git@github.com:your_github_username/authn-go.git authn 26 | cd authn 27 | git remote add upstream https://github.com/connectrpc/authn-go.git 28 | git fetch upstream 29 | ``` 30 | 31 | Make sure that the tests and the linters pass (you'll need `bash` and the 32 | latest stable Go release installed): 33 | 34 | ``` 35 | make 36 | ``` 37 | 38 | ## Making Changes 39 | 40 | Start by creating a new branch for your changes: 41 | 42 | ``` 43 | cd $GOPATH/src/connectrpc.com/authn 44 | git checkout main 45 | git fetch upstream 46 | git rebase upstream/main 47 | git checkout -b cool_new_feature 48 | ``` 49 | 50 | Make your changes, then ensure that `make` still passes. (You can use the 51 | standard `go build ./...` and `go test ./...` while you're coding.) When you're 52 | satisfied with your changes, push them to your fork. 53 | 54 | ``` 55 | git commit -a 56 | git push origin cool_new_feature 57 | ``` 58 | 59 | Then use the GitHub UI to open a pull request. 60 | 61 | At this point, you're waiting on us to review your changes. We *try* to respond 62 | to issues and pull requests within a few business days, and we may suggest some 63 | improvements or alternatives. Once your changes are approved, one of the 64 | project maintainers will merge them. 65 | 66 | We're much more likely to approve your changes if you: 67 | 68 | * Add tests for new functionality. 69 | * Write a [good commit message][commit-message]. 70 | * Maintain backward compatibility. 71 | 72 | [fork]: https://github.com/connectrpc/authn-go/fork 73 | [open-issue]: https://github.com/connectrpc/authn-go/issues/new 74 | [dco]: https://developercertificate.org 75 | [commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 76 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - dependabot 7 | categories: 8 | - title: Enhancements 9 | labels: 10 | - enhancement 11 | - title: Bugfixes 12 | labels: 13 | - bug 14 | - title: Other changes 15 | labels: 16 | - "*" 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [main] 5 | tags: ['v*'] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: '15 22 * * *' 10 | workflow_dispatch: {} # support manual runs 11 | permissions: 12 | contents: read 13 | jobs: 14 | ci: 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | go-version: [1.21.x, 1.22.x, 1.23.x] 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 1 24 | - name: Install Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ matrix.go-version }} 28 | - name: Test 29 | run: make test 30 | - name: Lint 31 | # Often, lint & gofmt guidelines depend on the Go version. To prevent 32 | # conflicting guidance, run only on the most recent supported version. 33 | # For the same reason, only check generated code on the most recent 34 | # supported version. 35 | if: matrix.go-version == '1.23.x' 36 | run: make checkgenerate && make lint 37 | -------------------------------------------------------------------------------- /.github/workflows/pr-title.yaml: -------------------------------------------------------------------------------- 1 | name: Lint PR Title 2 | # Prevent writing to the repository using the CI token. 3 | # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions 4 | permissions: 5 | pull-requests: read 6 | on: 7 | pull_request: 8 | # By default, a workflow only runs when a pull_request's activity type is opened, 9 | # synchronize, or reopened. We explicity override here so that PR titles are 10 | # re-linted when the PR text content is edited. 11 | types: 12 | - opened 13 | - edited 14 | - reopened 15 | - synchronize 16 | jobs: 17 | lint: 18 | uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.tmp/ 2 | *.pprof 3 | *.svg 4 | cover.out 5 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | errcheck: 3 | check-type-assertions: true 4 | forbidigo: 5 | forbid: 6 | - '^fmt\.Print' 7 | - '^log\.' 8 | - '^print$' 9 | - '^println$' 10 | - '^panic$' 11 | godox: 12 | # TODO, OPT, etc. comments are fine to commit. Use FIXME comments for 13 | # temporary hacks, and use godox to prevent committing them. 14 | keywords: [FIXME] 15 | varnamelen: 16 | ignore-decls: 17 | - T any 18 | - i int 19 | - wg sync.WaitGroup 20 | linters: 21 | enable-all: true 22 | disable: 23 | - cyclop # covered by gocyclo 24 | - depguard # unnecessary for small libraries 25 | - execinquery # deprecated since golangci v1.58 26 | - exportloopref # covered by copyloopvar 27 | - funlen # rely on code review to limit function length 28 | - gocognit # dubious "cognitive overhead" quantification 29 | - gofumpt # prefer standard gofmt 30 | - goimports # rely on gci instead 31 | - gomnd # some unnamed constants are okay 32 | - inamedparam # convention is not followed 33 | - ireturn # "accept interfaces, return structs" isn't ironclad 34 | - lll # don't want hard limits for line length 35 | - maintidx # covered by gocyclo 36 | - mnd # some unnamed constants are okay 37 | - nlreturn # generous whitespace violates house style 38 | - nonamedreturns # named returns are fine; it's *bare* returns that are bad 39 | - protogetter # too many false positives 40 | - testpackage # internal tests are fine 41 | - wrapcheck # don't _always_ need to wrap errors 42 | - wsl # generous whitespace violates house style 43 | issues: 44 | exclude: 45 | # Don't ban use of fmt.Errorf to create new errors, but the remaining 46 | # checks from err113 are useful. 47 | - "do not define dynamic errors.*" 48 | exclude-rules: 49 | # We need to init a global in-mem HTTP server for testable examples. 50 | - path: examples_test.go 51 | linters: [gocritic, gochecknoglobals, gosec] 52 | # Allow more lenient rules in example code for brevity. 53 | - path: examples_test.go 54 | linters: [exhaustruct, nilnil, varnamelen] 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023-2024 Buf Technologies, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | Maintainers 2 | =========== 3 | 4 | ## Current 5 | * [Akshay Shah](https://github.com/akshayjshah), [Buf](https://buf.build) 6 | * [Edward McFarlane](https://github.com/emcfarlane), [Buf](https://buf.build) 7 | * [Josh Humphries](https://github.com/jhump), [Buf](https://buf.build) 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # See https://tech.davis-hansson.com/p/make/ 2 | SHELL := bash 3 | .DELETE_ON_ERROR: 4 | .SHELLFLAGS := -eu -o pipefail -c 5 | .DEFAULT_GOAL := all 6 | MAKEFLAGS += --warn-undefined-variables 7 | MAKEFLAGS += --no-builtin-rules 8 | MAKEFLAGS += --no-print-directory 9 | BIN := .tmp/bin 10 | export PATH := $(BIN):$(PATH) 11 | export GOBIN := $(abspath $(BIN)) 12 | COPYRIGHT_YEARS := 2023-2024 13 | LICENSE_IGNORE := --ignore testdata/ 14 | BUF_VERSION := 1.39.0 15 | 16 | .PHONY: help 17 | help: ## Describe useful make targets 18 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-30s %s\n", $$1, $$2}' 19 | 20 | .PHONY: all 21 | all: ## Build, test, and lint (default) 22 | $(MAKE) test 23 | $(MAKE) lint 24 | 25 | .PHONY: clean 26 | clean: ## Delete intermediate build artifacts 27 | @# -X only removes untracked files, -d recurses into directories, -f actually removes files/dirs 28 | git clean -Xdf 29 | 30 | .PHONY: test 31 | test: build ## Run unit tests 32 | go test -vet=off -race -cover ./... 33 | 34 | .PHONY: build 35 | build: generate ## Build all packages 36 | go build ./... 37 | 38 | .PHONY: generate 39 | generate: $(BIN)/buf $(BIN)/protoc-gen-go $(BIN)/protoc-gen-connect-go $(BIN)/license-header ## Regenerate code and licenses 40 | rm -rf internal/gen 41 | PATH="$(abspath $(BIN))" buf generate 42 | license-header \ 43 | --license-type apache \ 44 | --copyright-holder "Buf Technologies, Inc." \ 45 | --year-range "$(COPYRIGHT_YEARS)" $(LICENSE_IGNORE) 46 | 47 | .PHONY: lint 48 | lint: $(BIN)/golangci-lint ## Lint 49 | go vet ./... 50 | golangci-lint run --modules-download-mode=readonly --timeout=3m0s 51 | buf lint 52 | buf format -d --exit-code 53 | 54 | .PHONY: lintfix 55 | lintfix: $(BIN)/golangci-lint ## Automatically fix some lint errors 56 | golangci-lint run --fix --modules-download-mode=readonly --timeout=3m0s 57 | buf format -w 58 | 59 | .PHONY: install 60 | install: ## Install all binaries 61 | go install ./... 62 | 63 | .PHONY: upgrade 64 | upgrade: ## Upgrade dependencies 65 | go get -u -t ./... && go mod tidy -v 66 | 67 | .PHONY: checkgenerate 68 | checkgenerate: 69 | @# Used in CI to verify that `make generate` doesn't produce a diff. 70 | test -z "$$(git status --porcelain | tee /dev/stderr)" 71 | 72 | $(BIN)/license-header: Makefile 73 | @mkdir -p $(@D) 74 | go install github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v${BUF_VERSION} 75 | 76 | $(BIN)/golangci-lint: Makefile 77 | @mkdir -p $(@D) 78 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.3 79 | 80 | $(BIN)/buf: Makefile 81 | @mkdir -p $(@D) 82 | go install github.com/bufbuild/buf/cmd/buf@v${BUF_VERSION} 83 | 84 | $(BIN)/protoc-gen-go: Makefile go.mod 85 | @mkdir -p $(@D) 86 | @# The version of protoc-gen-go is determined by the version in go.mod 87 | go install google.golang.org/protobuf/cmd/protoc-gen-go 88 | 89 | $(BIN)/protoc-gen-connect-go: Makefile go.mod 90 | @mkdir -p $(@D) 91 | @# The version of protoc-gen-connect-go is determined by the version in go.mod 92 | go install connectrpc.com/connect/cmd/protoc-gen-connect-go 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | authn 2 | ===== 3 | [![Build](https://github.com/connectrpc/authn-go/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/connectrpc/authn-go/actions/workflows/ci.yaml) 4 | [![Report Card](https://goreportcard.com/badge/connectrpc.com/authn)](https://goreportcard.com/report/connectrpc.com/authn) 5 | [![GoDoc](https://pkg.go.dev/badge/connectrpc.com/authn.svg)](https://pkg.go.dev/connectrpc.com/authn) 6 | [![Slack](https://img.shields.io/badge/slack-buf-%23e01563)][slack] 7 | 8 | `connectrpc.com/authn` provides authentication middleware for 9 | [Connect](https://connectrpc.com/). It works with any authentication scheme 10 | (including HTTP basic authentication, cookies, bearer tokens, and mutual TLS), 11 | and it's carefully designed to minimize the resource consumption of 12 | unauthenticated RPCs. Middleware built with `authn` covers both unary and 13 | streaming RPCs made with the Connect, gRPC, and gRPC-Web protocols. 14 | 15 | For more on Connect, see the [announcement blog post][blog], the documentation 16 | on [connectrpc.com][docs] (especially the [Getting Started] guide for Go), the 17 | [demo service][examples-go], or the [protocol specification][protocol]. 18 | 19 | ## A small example 20 | 21 | Curious what all this looks like in practice? From a [Protobuf 22 | schema](internal/proto/authn/ping/v1/ping.proto), we generate [a small RPC 23 | package](internal/gen/authn/ping/v1/pingv1connect/ping.connect.go). Using that 24 | package, we can build a server and wrap it with some basic authentication: 25 | 26 | ```go 27 | package main 28 | 29 | import ( 30 | "context" 31 | "crypto/subtle" 32 | "net/http" 33 | 34 | "connectrpc.com/authn" 35 | "connectrpc.com/authn/internal/gen/authn/ping/v1/pingv1connect" 36 | ) 37 | 38 | func authenticate(_ context.Context, req *http.Request) (any, error) { 39 | username, password, ok := req.BasicAuth() 40 | if !ok { 41 | return nil, authn.Errorf("invalid authorization") 42 | } 43 | if !equal(password, "open-sesame") { 44 | return nil, authn.Errorf("invalid password") 45 | } 46 | // The request is authenticated! We can propagate the authenticated user to 47 | // Connect interceptors and services by returning it: the middleware we're 48 | // about to construct will attach it to the context automatically. 49 | return username, nil 50 | } 51 | 52 | func equal(left, right string) bool { 53 | // Using subtle prevents some timing attacks. 54 | return subtle.ConstantTimeCompare([]byte(left), []byte(right)) == 1 55 | } 56 | 57 | func main() { 58 | mux := http.NewServeMux() 59 | service := &pingv1connect.UnimplementedPingServiceHandler{} 60 | mux.Handle(pingv1connect.NewPingServiceHandler(service)) 61 | 62 | middleware := authn.NewMiddleware(authenticate) 63 | handler := middleware.Wrap(mux) 64 | http.ListenAndServe("localhost:8080", handler) 65 | } 66 | ``` 67 | 68 | Cookie and token-based authentication is similar. Mutual TLS is a bit more 69 | complex, but [pkg.go.dev][godoc] includes a complete example. 70 | 71 | ## Ecosystem 72 | 73 | * [connect-go]: the Go implementation of Connect's RPC runtime 74 | * [examples-go]: service powering demo.connectrpc.com, including bidi streaming 75 | * [grpchealth]: gRPC-compatible health checks 76 | * [grpcreflect]: gRPC-compatible server reflection 77 | * [cors]: CORS support for Connect servers 78 | * [connect-es]: Type-safe APIs with Protobuf and TypeScript 79 | * [conformance]: Connect, gRPC, and gRPC-Web interoperability tests 80 | 81 | ## Status: Unstable 82 | 83 | This module isn't stable yet, but it's fairly small — we expect to reach 84 | a stable release quickly. 85 | 86 | It supports the three most recent major releases of Go. Keep in mind that [only 87 | the last two releases receive security patches][go-support-policy]. 88 | 89 | Within those parameters, `authn` follows semantic versioning. We will _not_ 90 | make breaking changes in the 1.x series of releases. 91 | 92 | ## Legal 93 | 94 | Offered under the [Apache 2 license][license]. 95 | 96 | [Getting Started]: https://connectrpc.com/docs/go/getting-started 97 | [blog]: https://buf.build/blog/connect-a-better-grpc 98 | [conformance]: https://github.com/connectrpc/conformance 99 | [connect-es]: https://github.com/connectrpc/connect-es 100 | [connect-go]: https://github.com/connectrpc/connect-go 101 | [cors]: https://github.com/connectrpc/cors-go 102 | [docs]: https://connectrpc.com 103 | [examples-go]: https://github.com/connectrpc/examples-go 104 | [go-support-policy]: https://golang.org/doc/devel/release#policy 105 | [godoc]: https://pkg.go.dev/connectrpc.com/authn 106 | [grpchealth]: https://github.com/connectrpc/grpchealth-go 107 | [grpcreflect]: https://github.com/connectrpc/grpcreflect-go 108 | [license]: https://github.com/connectrpc/authn-go/blob/main/LICENSE 109 | [protocol]: https://connectrpc.com/docs/protocol 110 | [slack]: https://buf.build/links/slack 111 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Security Policy 2 | =============== 3 | 4 | This project follows the [Connect security policy and reporting 5 | process](https://connectrpc.com/docs/governance/security). 6 | -------------------------------------------------------------------------------- /authn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package authn provides authentication middleware for [connect]. 16 | package authn 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "mime" 22 | "net/http" 23 | "net/url" 24 | "strings" 25 | 26 | "connectrpc.com/connect" 27 | ) 28 | 29 | type key int 30 | 31 | const infoKey key = iota 32 | 33 | // An AuthFunc authenticates an RPC. The function must return an error if the 34 | // request cannot be authenticated. The error is typically produced with 35 | // [Errorf], but any error will do. 36 | // 37 | // If requests are successfully authenticated, the authentication function may 38 | // return some information about the authenticated caller (or nil). If non-nil, 39 | // the information is automatically attached to the context using [SetInfo]. 40 | // 41 | // Implementations must be safe to call concurrently. 42 | type AuthFunc func(ctx context.Context, req *http.Request) (any, error) 43 | 44 | // SetInfo attaches authentication information to the context. It's often 45 | // useful in tests. 46 | // 47 | // [AuthFunc] implementations do not need to call SetInfo explicitly. Any 48 | // returned authentication information is automatically added to the context by 49 | // [Middleware]. 50 | func SetInfo(ctx context.Context, info any) context.Context { 51 | if info == nil { 52 | return ctx 53 | } 54 | return context.WithValue(ctx, infoKey, info) 55 | } 56 | 57 | // GetInfo retrieves authentication information, if any, from the request 58 | // context. 59 | func GetInfo(ctx context.Context) any { 60 | return ctx.Value(infoKey) 61 | } 62 | 63 | // WithoutInfo strips the authentication information, if any, from the provided 64 | // context. 65 | func WithoutInfo(ctx context.Context) context.Context { 66 | return context.WithValue(ctx, infoKey, nil) 67 | } 68 | 69 | // Errorf is a convenience function that returns an error coded with 70 | // [connect.CodeUnauthenticated]. 71 | func Errorf(template string, args ...any) *connect.Error { 72 | return connect.NewError(connect.CodeUnauthenticated, fmt.Errorf(template, args...)) 73 | } 74 | 75 | // InferProtocol returns the inferred RPC protocol. It is one of 76 | // [connect.ProtocolConnect], [connect.ProtocolGRPC], or [connect.ProtocolGRPCWeb]. 77 | func InferProtocol(request *http.Request) (string, bool) { 78 | const ( 79 | grpcContentTypeDefault = "application/grpc" 80 | grpcContentTypePrefix = "application/grpc+" 81 | grpcWebContentTypeDefault = "application/grpc-web" 82 | grpcWebContentTypePrefix = "application/grpc-web+" 83 | connectStreamingContentTypePrefix = "application/connect+" 84 | connectUnaryContentTypePrefix = "application/" 85 | connectUnaryMessageQueryParameter = "message" 86 | connectUnaryEncodingQueryParameter = "encoding" 87 | ) 88 | ctype := canonicalizeContentType(request.Header.Get("Content-Type")) 89 | isPost := request.Method == http.MethodPost 90 | isGet := request.Method == http.MethodGet 91 | switch { 92 | case isPost && (ctype == grpcContentTypeDefault || strings.HasPrefix(ctype, grpcContentTypePrefix)): 93 | return connect.ProtocolGRPC, true 94 | case isPost && (ctype == grpcWebContentTypeDefault || strings.HasPrefix(ctype, grpcWebContentTypePrefix)): 95 | return connect.ProtocolGRPCWeb, true 96 | case isPost && strings.HasPrefix(ctype, connectStreamingContentTypePrefix): 97 | return connect.ProtocolConnect, true 98 | case isPost && strings.HasPrefix(ctype, connectUnaryContentTypePrefix): 99 | return connect.ProtocolConnect, true 100 | case isGet: 101 | query := request.URL.Query() 102 | hasMessage := query.Has(connectUnaryMessageQueryParameter) 103 | hasEncoding := query.Has(connectUnaryEncodingQueryParameter) 104 | if !hasMessage || !hasEncoding { 105 | return "", false 106 | } 107 | return connect.ProtocolConnect, true 108 | default: 109 | return "", false 110 | } 111 | } 112 | 113 | // InferProcedure returns the inferred RPC procedure. It's returned in the form 114 | // "/service/method" if a valid suffix is found. If the request doesn't contain 115 | // a service and method, the entire path and false is returned. 116 | func InferProcedure(url *url.URL) (string, bool) { 117 | path := url.Path 118 | ultimate := strings.LastIndex(path, "/") 119 | if ultimate < 0 { 120 | return url.Path, false 121 | } 122 | penultimate := strings.LastIndex(path[:ultimate], "/") 123 | if penultimate < 0 { 124 | return url.Path, false 125 | } 126 | procedure := path[penultimate:] 127 | // Ensure that the service and method are non-empty. 128 | if ultimate == len(path)-1 || penultimate == ultimate-1 { 129 | return url.Path, false 130 | } 131 | return procedure, true 132 | } 133 | 134 | // BearerToken returns the bearer token provided in the request's Authorization 135 | // header, if any. 136 | func BearerToken(request *http.Request) (string, bool) { 137 | const prefix = "Bearer " 138 | auth := request.Header.Get("Authorization") 139 | // Case insensitive prefix match. See RFC 9110 Section 11.1. 140 | if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { 141 | return "", false 142 | } 143 | return auth[len(prefix):], true 144 | } 145 | 146 | // Middleware is server-side HTTP middleware that authenticates RPC requests. 147 | // In addition to rejecting unauthenticated requests, it can optionally attach 148 | // arbitrary information about the authenticated identity to the context. 149 | // 150 | // Middleware operates at a lower level than Connect interceptors, so the 151 | // server doesn't decompress and unmarshal the request until the caller has 152 | // been authenticated. 153 | type Middleware struct { 154 | auth AuthFunc 155 | errW *connect.ErrorWriter 156 | } 157 | 158 | // NewMiddleware constructs HTTP middleware using the supplied authentication 159 | // function. If authentication succeeds, the authentication information (if 160 | // any) will be attached to the context. Subsequent HTTP middleware, all RPC 161 | // interceptors, and application code may access it with [GetInfo]. 162 | // 163 | // In order to properly marshal errors, applications must pass NewMiddleware 164 | // the same handler options used when constructing Connect handlers. 165 | func NewMiddleware(auth AuthFunc, opts ...connect.HandlerOption) *Middleware { 166 | return &Middleware{ 167 | auth: auth, 168 | errW: connect.NewErrorWriter(opts...), 169 | } 170 | } 171 | 172 | // Wrap returns an HTTP handler that authenticates requests before forwarding 173 | // them to handler. 174 | func (m *Middleware) Wrap(handler http.Handler) http.Handler { 175 | return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 176 | ctx := request.Context() 177 | info, err := m.auth(ctx, request) 178 | if err != nil { 179 | _ = m.errW.Write(writer, request, err) 180 | return 181 | } 182 | if info != nil { 183 | ctx = SetInfo(ctx, info) 184 | request = request.WithContext(ctx) 185 | } 186 | handler.ServeHTTP(writer, request) 187 | }) 188 | } 189 | 190 | func canonicalizeContentType(contentType string) string { 191 | // Typically, clients send Content-Type in canonical form, without 192 | // parameters. In those cases, we'd like to avoid parsing and 193 | // canonicalization overhead. 194 | // 195 | // See https://www.rfc-editor.org/rfc/rfc2045.html#section-5.1 for a full 196 | // grammar. 197 | var slashes int 198 | for _, r := range contentType { 199 | switch { 200 | case r >= 'a' && r <= 'z': 201 | case r == '.' || r == '+' || r == '-': 202 | case r == '/': 203 | slashes++ 204 | default: 205 | return canonicalizeContentTypeSlow(contentType) 206 | } 207 | } 208 | if slashes == 1 { 209 | return contentType 210 | } 211 | return canonicalizeContentTypeSlow(contentType) 212 | } 213 | 214 | func canonicalizeContentTypeSlow(contentType string) string { 215 | base, params, err := mime.ParseMediaType(contentType) 216 | if err != nil { 217 | return contentType 218 | } 219 | // According to RFC 9110 Section 8.3.2, the charset parameter value should be treated as case-insensitive. 220 | // mime.FormatMediaType canonicalizes parameter names, but not parameter values, 221 | // because the case sensitivity of a parameter value depends on its semantics. 222 | // Therefore, the charset parameter value should be canonicalized here. 223 | // ref.) https://httpwg.org/specs/rfc9110.html#rfc.section.8.3.2 224 | if charset, ok := params["charset"]; ok { 225 | params["charset"] = strings.ToLower(charset) 226 | } 227 | return mime.FormatMediaType(base, params) 228 | } 229 | -------------------------------------------------------------------------------- /authn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package authn_test 16 | 17 | import ( 18 | "context" 19 | "io" 20 | "net/http" 21 | "net/http/httptest" 22 | "net/url" 23 | "strings" 24 | "testing" 25 | 26 | "connectrpc.com/authn" 27 | "connectrpc.com/connect" 28 | "github.com/stretchr/testify/assert" 29 | "github.com/stretchr/testify/require" 30 | ) 31 | 32 | const ( 33 | hero = "Ali Baba" 34 | passphrase = "opensesame" 35 | ) 36 | 37 | func TestMiddleware(t *testing.T) { 38 | t.Parallel() 39 | mux := http.NewServeMux() 40 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 41 | if r.Header.Get("Check-Info") != "" { 42 | assertInfo(r.Context(), t) 43 | } 44 | _, _ = io.WriteString(w, "ok") 45 | }) 46 | handler := authn.NewMiddleware(authenticate).Wrap(mux) 47 | server := httptest.NewServer(handler) 48 | 49 | assertResponse := func(headers http.Header, expectCode int) { 50 | req, err := http.NewRequestWithContext( 51 | context.Background(), 52 | http.MethodPost, 53 | server.URL+"/empty.v1/GetEmpty", 54 | strings.NewReader("{}"), 55 | ) 56 | require.NoError(t, err) 57 | for k, vals := range headers { 58 | for _, v := range vals { 59 | req.Header.Add(k, v) 60 | } 61 | } 62 | res, err := server.Client().Do(req) 63 | require.NoError(t, err) 64 | assert.Equal(t, expectCode, res.StatusCode) 65 | assert.NoError(t, res.Body.Close()) 66 | } 67 | // Middleware should authenticate non-RPC requests. 68 | assertResponse(http.Header{}, http.StatusUnauthorized) 69 | // RPCs without the right bearer token should be rejected. 70 | assertResponse( 71 | http.Header{"Content-Type": []string{"application/json"}}, 72 | http.StatusUnauthorized, 73 | ) 74 | // RPCs with the right token should be allowed. 75 | assertResponse( 76 | http.Header{ 77 | "Content-Type": []string{"application/json"}, 78 | "Authorization": []string{"Bearer " + passphrase}, 79 | "Check-Info": []string{"1"}, // verify that auth info is attached to context 80 | }, 81 | http.StatusOK, 82 | ) 83 | } 84 | 85 | func assertInfo(ctx context.Context, tb testing.TB) { 86 | tb.Helper() 87 | info := authn.GetInfo(ctx) 88 | if info == nil { 89 | tb.Fatal("no authentication info") 90 | } 91 | name, ok := info.(string) 92 | assert.True(tb, ok, "got info of type %T, expected string", info) 93 | assert.Equal(tb, hero, name) 94 | if id := authn.GetInfo(authn.WithoutInfo(ctx)); id != nil { 95 | tb.Fatalf("got info %v after WithoutInfo", id) 96 | } 97 | } 98 | 99 | func authenticate(_ context.Context, req *http.Request) (any, error) { 100 | token, ok := authn.BearerToken(req) 101 | if !ok { 102 | err := authn.Errorf("expected Bearer authentication scheme") 103 | err.Meta().Set("WWW-Authenticate", "Bearer") 104 | return nil, err 105 | } 106 | if token != passphrase { 107 | return nil, authn.Errorf("%q is not the magic passphrase", token) 108 | } 109 | return hero, nil 110 | } 111 | 112 | func TestInferProcedures(t *testing.T) { 113 | t.Parallel() 114 | tests := []struct { 115 | name string 116 | url string 117 | want string 118 | valid bool 119 | }{ 120 | {name: "simple", url: "http://localhost:8080/foo", want: "/foo", valid: false}, 121 | {name: "service", url: "http://localhost:8080/service/bar", want: "/service/bar", valid: true}, 122 | {name: "trailing", url: "http://localhost:8080/service/bar/", want: "/service/bar/", valid: false}, 123 | {name: "subroute", url: "http://localhost:8080/api/service/bar", want: "/service/bar", valid: true}, 124 | {name: "subrouteTrailing", url: "http://localhost:8080/api/service/bar/", want: "/api/service/bar/", valid: false}, 125 | {name: "missingService", url: "http://localhost:8080//foo", want: "//foo", valid: false}, 126 | {name: "missingMethod", url: "http://localhost:8080/foo//", want: "/foo//", valid: false}, 127 | { 128 | name: "real", 129 | url: "http://localhost:8080/connect.ping.v1.PingService/Ping", 130 | want: "/connect.ping.v1.PingService/Ping", 131 | valid: true, 132 | }, 133 | } 134 | for _, testcase := range tests { 135 | testcase := testcase 136 | t.Run(testcase.name, func(t *testing.T) { 137 | t.Parallel() 138 | url, err := url.Parse(testcase.url) 139 | require.NoError(t, err) 140 | got, valid := authn.InferProcedure(url) 141 | assert.Equal(t, testcase.want, got) 142 | assert.Equal(t, testcase.valid, valid) 143 | }) 144 | } 145 | } 146 | 147 | func TestInferProtocol(t *testing.T) { 148 | t.Parallel() 149 | tests := []struct { 150 | name string 151 | contentType string 152 | method string 153 | params url.Values 154 | want string 155 | valid bool 156 | }{{ 157 | name: "connectUnary", 158 | contentType: "application/json", 159 | method: http.MethodPost, 160 | params: nil, 161 | want: connect.ProtocolConnect, 162 | valid: true, 163 | }, { 164 | name: "connectStreaming", 165 | contentType: "application/connec+json", 166 | method: http.MethodPost, 167 | params: nil, 168 | want: connect.ProtocolConnect, 169 | valid: true, 170 | }, { 171 | name: "grpcWeb", 172 | contentType: "application/grpc-web", 173 | method: http.MethodPost, 174 | params: nil, 175 | want: connect.ProtocolGRPCWeb, 176 | valid: true, 177 | }, { 178 | name: "grpc", 179 | contentType: "application/grpc", 180 | method: http.MethodPost, 181 | params: nil, 182 | want: connect.ProtocolGRPC, 183 | valid: true, 184 | }, { 185 | name: "connectGet", 186 | contentType: "", 187 | method: http.MethodGet, 188 | params: url.Values{"message": []string{"{}"}, "encoding": []string{"json"}}, 189 | want: connect.ProtocolConnect, 190 | valid: true, 191 | }, { 192 | name: "connectGetProto", 193 | contentType: "", 194 | method: http.MethodGet, 195 | params: url.Values{"message": []string{""}, "encoding": []string{"proto"}}, 196 | want: connect.ProtocolConnect, 197 | valid: true, 198 | }, { 199 | name: "connectGetMissingParams", 200 | contentType: "", 201 | method: http.MethodGet, 202 | params: nil, 203 | want: "", 204 | valid: false, 205 | }, { 206 | name: "connectGetMissingParam-Message", 207 | contentType: "", 208 | method: http.MethodGet, 209 | params: url.Values{"encoding": []string{"json"}}, 210 | want: "", 211 | valid: false, 212 | }, { 213 | name: "connectGetMissingParam-Encoding", 214 | contentType: "", 215 | method: http.MethodGet, 216 | params: url.Values{"message": []string{"{}"}}, 217 | want: "", 218 | valid: false, 219 | }, { 220 | name: "connectPutContentType", 221 | contentType: "application/connect+json", 222 | method: http.MethodPut, 223 | params: nil, 224 | want: "", 225 | valid: false, 226 | }, { 227 | name: "nakedGet", 228 | contentType: "", 229 | method: http.MethodGet, 230 | params: nil, 231 | want: "", 232 | valid: false, 233 | }, { 234 | name: "unknown", 235 | contentType: "text/html", 236 | method: http.MethodPost, 237 | params: nil, 238 | want: "", 239 | valid: false, 240 | }} 241 | for _, testcase := range tests { 242 | testcase := testcase 243 | t.Run(testcase.name, func(t *testing.T) { 244 | t.Parallel() 245 | req := httptest.NewRequest(testcase.method, "http://localhost:8080/service/Method", nil) 246 | if testcase.contentType != "" { 247 | req.Header.Set("Content-Type", testcase.contentType) 248 | } 249 | if testcase.params != nil { 250 | req.URL.RawQuery = testcase.params.Encode() 251 | } 252 | req.Method = testcase.method 253 | got, valid := authn.InferProtocol(req) 254 | assert.Equal(t, testcase.want, got, "protocol") 255 | assert.Equal(t, testcase.valid, valid, "valid") 256 | }) 257 | } 258 | } 259 | 260 | func TestBearerTokenCaseInsensitive(t *testing.T) { 261 | t.Parallel() 262 | req := httptest.NewRequest(http.MethodGet, "http://localhost:8080/service/Method", nil) 263 | req.Header.Set("Authorization", "bearer "+passphrase) 264 | token, ok := authn.BearerToken(req) 265 | assert.True(t, ok) 266 | assert.Equal(t, passphrase, token) 267 | } 268 | -------------------------------------------------------------------------------- /buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | managed: 3 | enabled: true 4 | override: 5 | - file_option: go_package_prefix 6 | value: connectrpc.com/authn/internal/gen 7 | plugins: 8 | - local: protoc-gen-go 9 | out: internal/gen 10 | opt: paths=source_relative 11 | - local: protoc-gen-connect-go 12 | out: internal/gen 13 | opt: paths=source_relative 14 | -------------------------------------------------------------------------------- /buf.yaml: -------------------------------------------------------------------------------- 1 | version: v2 2 | modules: 3 | - path: internal/proto 4 | lint: 5 | use: 6 | - DEFAULT 7 | disallow_comment_ignores: true 8 | breaking: 9 | use: 10 | - WIRE_JSON 11 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package authn_test 16 | 17 | import ( 18 | "context" 19 | "crypto/rand" 20 | "crypto/rsa" 21 | "crypto/subtle" 22 | "crypto/tls" 23 | "crypto/x509" 24 | "crypto/x509/pkix" 25 | "encoding/base64" 26 | "encoding/pem" 27 | "errors" 28 | "fmt" 29 | "io" 30 | "math/big" 31 | "net" 32 | "net/http" 33 | "net/http/httptest" 34 | "time" 35 | 36 | "connectrpc.com/authn" 37 | pingv1 "connectrpc.com/authn/internal/gen/authn/ping/v1" 38 | "connectrpc.com/authn/internal/gen/authn/ping/v1/pingv1connect" 39 | "connectrpc.com/connect" 40 | ) 41 | 42 | func Example_basicAuth() { 43 | // This example shows how to use this package with HTTP basic authentication. 44 | // Any header-based authentication (including cookies and bearer tokens) 45 | // works similarly. 46 | 47 | // First, we define our authentication logic and use it to build middleware. 48 | authenticate := func(_ context.Context, req *http.Request) (any, error) { 49 | username, password, ok := req.BasicAuth() 50 | if !ok { 51 | return nil, authn.Errorf("invalid authorization") 52 | } 53 | if !equal(password, "open-sesame") { 54 | return nil, authn.Errorf("invalid password") 55 | } 56 | // The request is authenticated! We can propagate the authenticated user to 57 | // Connect interceptors and services by returning it: the middleware we're 58 | // about to construct will attach it to the context automatically. 59 | fmt.Println("authenticated request from", username) 60 | return username, nil 61 | } 62 | middleware := authn.NewMiddleware(authenticate) 63 | 64 | // Next, we build our Connect handler. 65 | mux := http.NewServeMux() 66 | service := &pingHandler{} 67 | mux.Handle(pingv1connect.NewPingServiceHandler(service)) 68 | 69 | // Finally, we wrap the handler with our middleware and start our server. 70 | handler := middleware.Wrap(mux) 71 | server := httptest.NewServer(handler) 72 | defer server.Close() 73 | 74 | // Clients authenticate by setting the standard Authorization header. 75 | client := pingv1connect.NewPingServiceClient(http.DefaultClient, server.URL) 76 | req := connect.NewRequest(&pingv1.PingRequest{}) 77 | req.Header().Set( 78 | "Authorization", 79 | "Basic "+base64.StdEncoding.EncodeToString([]byte("Aladdin:open-sesame")), 80 | ) 81 | _, err := client.Ping(context.Background(), req) 82 | if err != nil { 83 | fmt.Printf("unexpected error: %v\n", err) 84 | return 85 | } 86 | fmt.Println("client received response") 87 | 88 | // Output: 89 | // authenticated request from Aladdin 90 | // client received response 91 | } 92 | 93 | func Example_bearerToken() { 94 | // This example shows how to use this package with bearer token authentication. 95 | // Any header-based authentication (including cookies and HTTP basic auth) 96 | // works similarly. 97 | 98 | // We'll use a simple allow list to demonstrate how to add authorization logic 99 | // conditionally based on the request's procedure. 100 | allowList := map[string]struct{}{ 101 | // Procedure constants are available in the generated code. 102 | pingv1connect.PingServicePingProcedure: {}, 103 | } 104 | // And a simple token-to-user map to demonstrate how to authenticate 105 | // requests based on a bearer token. 106 | tokenToUser := map[string]string{ 107 | "open-sesame": "Aladdin", 108 | } 109 | 110 | // First, we define our authentication logic and use it to build middleware. 111 | authenticate := func(_ context.Context, req *http.Request) (any, error) { 112 | // Infer the procedure from the request URL. 113 | procedure, _ := authn.InferProcedure(req.URL) 114 | // Extract the bearer token from the Authorization header. 115 | token, ok := authn.BearerToken(req) 116 | if !ok { 117 | // We'll allow unauthenticated access to the ping procedure. 118 | if _, ok := allowList[procedure]; ok { 119 | fmt.Println("no authentication required for", procedure) 120 | return nil, nil // no authentication required 121 | } 122 | fmt.Println("authentication required for", procedure) 123 | err := authn.Errorf("invalid authorization") 124 | err.Meta().Set("WWW-Authenticate", "Bearer") 125 | return nil, err 126 | } 127 | user, ok := tokenToUser[token] 128 | if !ok { 129 | return nil, authn.Errorf("invalid token") 130 | } 131 | // The request is authenticated! 132 | fmt.Println("authenticated request from", user, "for", procedure) 133 | return user, nil 134 | } 135 | middleware := authn.NewMiddleware(authenticate) 136 | 137 | // Next, we build our Connect handler. 138 | mux := http.NewServeMux() 139 | service := &pingHandler{} 140 | mux.Handle(pingv1connect.NewPingServiceHandler(service)) 141 | 142 | // Finally, we wrap the handler with our middleware and start our server. 143 | handler := middleware.Wrap(mux) 144 | server := httptest.NewServer(handler) 145 | defer server.Close() 146 | 147 | // Create an unauthenticated call to the ping procedure. 148 | client := pingv1connect.NewPingServiceClient(http.DefaultClient, server.URL) 149 | if _, err := client.Ping(context.Background(), connect.NewRequest( 150 | &pingv1.PingRequest{Text: "hello"}, 151 | )); err != nil { 152 | fmt.Printf("unexpected error: %v\n", err) 153 | return 154 | } 155 | fmt.Println("client received response") 156 | 157 | // Create an unauthenticated call to the echo procedure. 158 | if _, err := client.Echo(context.Background(), connect.NewRequest( 159 | &pingv1.EchoRequest{Text: "hello"}, 160 | )); connect.CodeOf(err) != connect.CodeUnauthenticated { 161 | fmt.Printf("unexpected error: %v\n", err) 162 | return 163 | } 164 | fmt.Println("client unauthorized") 165 | 166 | // Create an authenticated call to the echo procedure. 167 | req := connect.NewRequest(&pingv1.EchoRequest{Text: "hello"}) 168 | req.Header().Set("Authorization", "Bearer open-sesame") 169 | if _, err := client.Echo(context.Background(), req); err != nil { 170 | fmt.Printf("unexpected error: %v\n", err) 171 | return 172 | } 173 | fmt.Println("client received response") 174 | 175 | // Output: 176 | // no authentication required for /authn.ping.v1.PingService/Ping 177 | // client received response 178 | // authentication required for /authn.ping.v1.PingService/Echo 179 | // client unauthorized 180 | // authenticated request from Aladdin for /authn.ping.v1.PingService/Echo 181 | // client received response 182 | } 183 | 184 | func Example_mutualTLS() { 185 | // This example shows how to use this package with mutual TLS. 186 | // First, we define our authentication logic and use it to build middleware. 187 | authenticate := func(_ context.Context, req *http.Request) (any, error) { 188 | tls := req.TLS 189 | if tls == nil { 190 | return nil, authn.Errorf("TLS required") 191 | } 192 | if len(tls.VerifiedChains) == 0 || len(tls.VerifiedChains[0]) == 0 { 193 | return nil, authn.Errorf("could not verify peer certificate") 194 | } 195 | name := tls.VerifiedChains[0][0].Subject.CommonName 196 | if !equal(name, "Aladdin") { // hardcode example credentials 197 | return nil, authn.Errorf("invalid subject common name %q", name) 198 | } 199 | // The request is authenticated! We can propagate the authenticated user to 200 | // Connect interceptors and services by returning it: the middleware we're 201 | // about to construct will attach it to the context automatically. 202 | fmt.Println("authenticated request from", name) 203 | return name, nil 204 | } 205 | middleware := authn.NewMiddleware(authenticate) 206 | 207 | // Next, we build our Connect handler. 208 | mux := http.NewServeMux() 209 | service := &pingv1connect.UnimplementedPingServiceHandler{} 210 | mux.Handle(pingv1connect.NewPingServiceHandler(service)) 211 | 212 | // Finally, we wrap the handler with our middleware and start the server. 213 | // Creating server and client TLS configurations is particularly verbose in 214 | // examples, where we need to set up a complete self-signed chain of trust. 215 | clientTLS, serverTLS, err := newTLSConfigs("Aladdin", "Cave of Wonders") 216 | if err != nil { 217 | fmt.Printf("error creating TLS configs: %v\n", err) 218 | return 219 | } 220 | handler := middleware.Wrap(mux) 221 | server := httptest.NewUnstartedServer(handler) 222 | server.TLS = serverTLS 223 | server.StartTLS() 224 | defer server.Close() 225 | 226 | // Clients must configure their underlying HTTP clients to present a valid 227 | // certificate. 228 | httpClient := &http.Client{ 229 | Transport: &http.Transport{TLSClientConfig: clientTLS}, 230 | } 231 | client := pingv1connect.NewPingServiceClient(httpClient, server.URL) 232 | _, err = client.Ping( 233 | context.Background(), 234 | connect.NewRequest(&pingv1.PingRequest{}), 235 | ) 236 | 237 | // We're using the UnimplementedPingServiceHandler stub, so authenticated 238 | // clients should receive an error with CodeUnimplemented. 239 | if connect.CodeOf(err) == connect.CodeUnimplemented { 240 | fmt.Println("client received response") 241 | } else { 242 | fmt.Printf("unexpected error: %v\n", err) 243 | } 244 | 245 | // Output: 246 | // authenticated request from Aladdin 247 | // client received response 248 | } 249 | 250 | func newTLSConfigs(clientName, serverName string) (client *tls.Config, server *tls.Config, _ error) { 251 | caCertPEM, caKeyPEM, err := createCertificateAuthority() 252 | if err != nil { 253 | return nil, nil, fmt.Errorf("create certificate authority: %w", err) 254 | } 255 | certPool := x509.NewCertPool() 256 | if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok { 257 | return nil, nil, errors.New("failed to append certs to pool") 258 | } 259 | serverCertificate, err := newCertificate(caCertPEM, caKeyPEM, serverName) 260 | if err != nil { 261 | return nil, nil, fmt.Errorf("create server certificate: %w", err) 262 | } 263 | clientCertificate, err := newCertificate(caCertPEM, caKeyPEM, clientName) 264 | if err != nil { 265 | return nil, nil, fmt.Errorf("create client certificate: %w", err) 266 | } 267 | clientTLS := &tls.Config{ 268 | Certificates: []tls.Certificate{clientCertificate}, 269 | RootCAs: certPool, 270 | MinVersion: tls.VersionTLS12, 271 | } 272 | serverTLS := &tls.Config{ 273 | ClientAuth: tls.RequireAndVerifyClientCert, 274 | Certificates: []tls.Certificate{serverCertificate}, 275 | ClientCAs: certPool, 276 | MinVersion: tls.VersionTLS12, 277 | } 278 | return clientTLS, serverTLS, nil 279 | } 280 | 281 | func createCertificateAuthority() ([]byte, []byte, error) { 282 | caCert := &x509.Certificate{ 283 | SerialNumber: big.NewInt(2021), 284 | Subject: pkix.Name{ 285 | Organization: []string{"Acme Co"}, 286 | }, 287 | NotBefore: time.Now().AddDate(-1, 0, 0), 288 | NotAfter: time.Now().AddDate(10, 0, 0), 289 | IsCA: true, 290 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 291 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 292 | BasicConstraintsValid: true, 293 | } 294 | caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) 295 | if err != nil { 296 | return nil, nil, err 297 | } 298 | caBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey) 299 | if err != nil { 300 | return nil, nil, err 301 | } 302 | caPEM := pem.EncodeToMemory(&pem.Block{ 303 | Type: "CERTIFICATE", 304 | Bytes: caBytes, 305 | }) 306 | caPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ 307 | Type: "RSA PRIVATE KEY", 308 | Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), 309 | }) 310 | return caPEM, caPrivKeyPEM, nil 311 | } 312 | 313 | func newCertificate(caCertPEM, caKeyPEM []byte, commonName string) (tls.Certificate, error) { 314 | keyPEMBlock, _ := pem.Decode(caKeyPEM) 315 | privateKey, err := x509.ParsePKCS1PrivateKey(keyPEMBlock.Bytes) 316 | if err != nil { 317 | return tls.Certificate{}, err 318 | } 319 | certPEMBlock, _ := pem.Decode(caCertPEM) 320 | parent, err := x509.ParseCertificate(certPEMBlock.Bytes) 321 | if err != nil { 322 | return tls.Certificate{}, err 323 | } 324 | cert := &x509.Certificate{ 325 | SerialNumber: big.NewInt(1658), 326 | Subject: pkix.Name{ 327 | Organization: []string{"Acme Co"}, 328 | CommonName: commonName, 329 | }, 330 | IPAddresses: []net.IP{ 331 | net.IPv4(127, 0, 0, 1), 332 | net.IPv6loopback, 333 | net.IPv4(0, 0, 0, 0), 334 | net.IPv6zero, 335 | }, 336 | NotBefore: time.Now().AddDate(-1, 0, 0), 337 | NotAfter: time.Now().AddDate(10, 0, 0), 338 | SubjectKeyId: []byte{1, 2, 3, 4, 6}, 339 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 340 | KeyUsage: x509.KeyUsageDigitalSignature, 341 | } 342 | certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) 343 | if err != nil { 344 | return tls.Certificate{}, err 345 | } 346 | certBytes, err := x509.CreateCertificate(rand.Reader, cert, parent, &certPrivKey.PublicKey, privateKey) 347 | if err != nil { 348 | return tls.Certificate{}, err 349 | } 350 | certPEM := pem.EncodeToMemory(&pem.Block{ 351 | Type: "CERTIFICATE", 352 | Bytes: certBytes, 353 | }) 354 | certPrivKeyPEM := pem.EncodeToMemory(&pem.Block{ 355 | Type: "RSA PRIVATE KEY", 356 | Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), 357 | }) 358 | return tls.X509KeyPair(certPEM, certPrivKeyPEM) 359 | } 360 | 361 | func equal(left, right string) bool { 362 | // Using subtle prevents some timing attacks. 363 | return subtle.ConstantTimeCompare([]byte(left), []byte(right)) == 1 364 | } 365 | 366 | type pingHandler struct { 367 | pingv1connect.UnimplementedPingServiceHandler 368 | } 369 | 370 | func (pingHandler) Ping(_ context.Context, req *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { 371 | return connect.NewResponse(&pingv1.PingResponse{Text: req.Msg.Text}), nil 372 | } 373 | 374 | func (pingHandler) Echo(_ context.Context, req *connect.Request[pingv1.EchoRequest]) (*connect.Response[pingv1.EchoResponse], error) { 375 | return connect.NewResponse(&pingv1.EchoResponse{Text: req.Msg.Text}), nil 376 | } 377 | 378 | func (pingHandler) PingStream(_ context.Context, stream *connect.BidiStream[pingv1.PingStreamRequest, pingv1.PingStreamResponse]) error { 379 | for { 380 | req, err := stream.Receive() 381 | if err != nil { 382 | if errors.Is(err, io.EOF) { 383 | return nil 384 | } 385 | return err 386 | } 387 | if err := stream.Send(&pingv1.PingStreamResponse{Text: req.Text}); err != nil { 388 | return err 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module connectrpc.com/authn 2 | 3 | go 1.21 4 | 5 | require ( 6 | connectrpc.com/connect v1.16.2 7 | github.com/stretchr/testify v1.9.0 8 | google.golang.org/protobuf v1.34.2 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/pmezard/go-difflib v1.0.0 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE= 2 | connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 6 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 10 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 11 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 12 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 13 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 14 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 15 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 16 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 20 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 21 | -------------------------------------------------------------------------------- /internal/gen/authn/ping/v1/ping.pb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical location for this file is 16 | // https://github.com/connectrpc/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. 17 | 18 | // Code generated by protoc-gen-go. DO NOT EDIT. 19 | // versions: 20 | // protoc-gen-go v1.34.2 21 | // protoc (unknown) 22 | // source: authn/ping/v1/ping.proto 23 | 24 | package pingv1 25 | 26 | import ( 27 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 28 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 29 | reflect "reflect" 30 | sync "sync" 31 | ) 32 | 33 | const ( 34 | // Verify that this generated code is sufficiently up-to-date. 35 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 36 | // Verify that runtime/protoimpl is sufficiently up-to-date. 37 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 38 | ) 39 | 40 | type PingRequest struct { 41 | state protoimpl.MessageState 42 | sizeCache protoimpl.SizeCache 43 | unknownFields protoimpl.UnknownFields 44 | 45 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 46 | } 47 | 48 | func (x *PingRequest) Reset() { 49 | *x = PingRequest{} 50 | if protoimpl.UnsafeEnabled { 51 | mi := &file_authn_ping_v1_ping_proto_msgTypes[0] 52 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 53 | ms.StoreMessageInfo(mi) 54 | } 55 | } 56 | 57 | func (x *PingRequest) String() string { 58 | return protoimpl.X.MessageStringOf(x) 59 | } 60 | 61 | func (*PingRequest) ProtoMessage() {} 62 | 63 | func (x *PingRequest) ProtoReflect() protoreflect.Message { 64 | mi := &file_authn_ping_v1_ping_proto_msgTypes[0] 65 | if protoimpl.UnsafeEnabled && x != nil { 66 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 67 | if ms.LoadMessageInfo() == nil { 68 | ms.StoreMessageInfo(mi) 69 | } 70 | return ms 71 | } 72 | return mi.MessageOf(x) 73 | } 74 | 75 | // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. 76 | func (*PingRequest) Descriptor() ([]byte, []int) { 77 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{0} 78 | } 79 | 80 | func (x *PingRequest) GetText() string { 81 | if x != nil { 82 | return x.Text 83 | } 84 | return "" 85 | } 86 | 87 | type PingResponse struct { 88 | state protoimpl.MessageState 89 | sizeCache protoimpl.SizeCache 90 | unknownFields protoimpl.UnknownFields 91 | 92 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 93 | } 94 | 95 | func (x *PingResponse) Reset() { 96 | *x = PingResponse{} 97 | if protoimpl.UnsafeEnabled { 98 | mi := &file_authn_ping_v1_ping_proto_msgTypes[1] 99 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 100 | ms.StoreMessageInfo(mi) 101 | } 102 | } 103 | 104 | func (x *PingResponse) String() string { 105 | return protoimpl.X.MessageStringOf(x) 106 | } 107 | 108 | func (*PingResponse) ProtoMessage() {} 109 | 110 | func (x *PingResponse) ProtoReflect() protoreflect.Message { 111 | mi := &file_authn_ping_v1_ping_proto_msgTypes[1] 112 | if protoimpl.UnsafeEnabled && x != nil { 113 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 114 | if ms.LoadMessageInfo() == nil { 115 | ms.StoreMessageInfo(mi) 116 | } 117 | return ms 118 | } 119 | return mi.MessageOf(x) 120 | } 121 | 122 | // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. 123 | func (*PingResponse) Descriptor() ([]byte, []int) { 124 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{1} 125 | } 126 | 127 | func (x *PingResponse) GetText() string { 128 | if x != nil { 129 | return x.Text 130 | } 131 | return "" 132 | } 133 | 134 | type EchoRequest struct { 135 | state protoimpl.MessageState 136 | sizeCache protoimpl.SizeCache 137 | unknownFields protoimpl.UnknownFields 138 | 139 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 140 | } 141 | 142 | func (x *EchoRequest) Reset() { 143 | *x = EchoRequest{} 144 | if protoimpl.UnsafeEnabled { 145 | mi := &file_authn_ping_v1_ping_proto_msgTypes[2] 146 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 147 | ms.StoreMessageInfo(mi) 148 | } 149 | } 150 | 151 | func (x *EchoRequest) String() string { 152 | return protoimpl.X.MessageStringOf(x) 153 | } 154 | 155 | func (*EchoRequest) ProtoMessage() {} 156 | 157 | func (x *EchoRequest) ProtoReflect() protoreflect.Message { 158 | mi := &file_authn_ping_v1_ping_proto_msgTypes[2] 159 | if protoimpl.UnsafeEnabled && x != nil { 160 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 161 | if ms.LoadMessageInfo() == nil { 162 | ms.StoreMessageInfo(mi) 163 | } 164 | return ms 165 | } 166 | return mi.MessageOf(x) 167 | } 168 | 169 | // Deprecated: Use EchoRequest.ProtoReflect.Descriptor instead. 170 | func (*EchoRequest) Descriptor() ([]byte, []int) { 171 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{2} 172 | } 173 | 174 | func (x *EchoRequest) GetText() string { 175 | if x != nil { 176 | return x.Text 177 | } 178 | return "" 179 | } 180 | 181 | type EchoResponse struct { 182 | state protoimpl.MessageState 183 | sizeCache protoimpl.SizeCache 184 | unknownFields protoimpl.UnknownFields 185 | 186 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 187 | } 188 | 189 | func (x *EchoResponse) Reset() { 190 | *x = EchoResponse{} 191 | if protoimpl.UnsafeEnabled { 192 | mi := &file_authn_ping_v1_ping_proto_msgTypes[3] 193 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 194 | ms.StoreMessageInfo(mi) 195 | } 196 | } 197 | 198 | func (x *EchoResponse) String() string { 199 | return protoimpl.X.MessageStringOf(x) 200 | } 201 | 202 | func (*EchoResponse) ProtoMessage() {} 203 | 204 | func (x *EchoResponse) ProtoReflect() protoreflect.Message { 205 | mi := &file_authn_ping_v1_ping_proto_msgTypes[3] 206 | if protoimpl.UnsafeEnabled && x != nil { 207 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 208 | if ms.LoadMessageInfo() == nil { 209 | ms.StoreMessageInfo(mi) 210 | } 211 | return ms 212 | } 213 | return mi.MessageOf(x) 214 | } 215 | 216 | // Deprecated: Use EchoResponse.ProtoReflect.Descriptor instead. 217 | func (*EchoResponse) Descriptor() ([]byte, []int) { 218 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{3} 219 | } 220 | 221 | func (x *EchoResponse) GetText() string { 222 | if x != nil { 223 | return x.Text 224 | } 225 | return "" 226 | } 227 | 228 | type PingStreamRequest struct { 229 | state protoimpl.MessageState 230 | sizeCache protoimpl.SizeCache 231 | unknownFields protoimpl.UnknownFields 232 | 233 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 234 | } 235 | 236 | func (x *PingStreamRequest) Reset() { 237 | *x = PingStreamRequest{} 238 | if protoimpl.UnsafeEnabled { 239 | mi := &file_authn_ping_v1_ping_proto_msgTypes[4] 240 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 241 | ms.StoreMessageInfo(mi) 242 | } 243 | } 244 | 245 | func (x *PingStreamRequest) String() string { 246 | return protoimpl.X.MessageStringOf(x) 247 | } 248 | 249 | func (*PingStreamRequest) ProtoMessage() {} 250 | 251 | func (x *PingStreamRequest) ProtoReflect() protoreflect.Message { 252 | mi := &file_authn_ping_v1_ping_proto_msgTypes[4] 253 | if protoimpl.UnsafeEnabled && x != nil { 254 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 255 | if ms.LoadMessageInfo() == nil { 256 | ms.StoreMessageInfo(mi) 257 | } 258 | return ms 259 | } 260 | return mi.MessageOf(x) 261 | } 262 | 263 | // Deprecated: Use PingStreamRequest.ProtoReflect.Descriptor instead. 264 | func (*PingStreamRequest) Descriptor() ([]byte, []int) { 265 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{4} 266 | } 267 | 268 | func (x *PingStreamRequest) GetText() string { 269 | if x != nil { 270 | return x.Text 271 | } 272 | return "" 273 | } 274 | 275 | type PingStreamResponse struct { 276 | state protoimpl.MessageState 277 | sizeCache protoimpl.SizeCache 278 | unknownFields protoimpl.UnknownFields 279 | 280 | Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` 281 | } 282 | 283 | func (x *PingStreamResponse) Reset() { 284 | *x = PingStreamResponse{} 285 | if protoimpl.UnsafeEnabled { 286 | mi := &file_authn_ping_v1_ping_proto_msgTypes[5] 287 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 288 | ms.StoreMessageInfo(mi) 289 | } 290 | } 291 | 292 | func (x *PingStreamResponse) String() string { 293 | return protoimpl.X.MessageStringOf(x) 294 | } 295 | 296 | func (*PingStreamResponse) ProtoMessage() {} 297 | 298 | func (x *PingStreamResponse) ProtoReflect() protoreflect.Message { 299 | mi := &file_authn_ping_v1_ping_proto_msgTypes[5] 300 | if protoimpl.UnsafeEnabled && x != nil { 301 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 302 | if ms.LoadMessageInfo() == nil { 303 | ms.StoreMessageInfo(mi) 304 | } 305 | return ms 306 | } 307 | return mi.MessageOf(x) 308 | } 309 | 310 | // Deprecated: Use PingStreamResponse.ProtoReflect.Descriptor instead. 311 | func (*PingStreamResponse) Descriptor() ([]byte, []int) { 312 | return file_authn_ping_v1_ping_proto_rawDescGZIP(), []int{5} 313 | } 314 | 315 | func (x *PingStreamResponse) GetText() string { 316 | if x != nil { 317 | return x.Text 318 | } 319 | return "" 320 | } 321 | 322 | var File_authn_ping_v1_ping_proto protoreflect.FileDescriptor 323 | 324 | var file_authn_ping_v1_ping_proto_rawDesc = []byte{ 325 | 0x0a, 0x18, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x76, 0x31, 0x2f, 326 | 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x61, 0x75, 0x74, 0x68, 327 | 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x22, 0x21, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 328 | 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 329 | 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x22, 0x0a, 0x0c, 330 | 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 331 | 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 332 | 0x22, 0x21, 0x0a, 0x0b, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 333 | 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 334 | 0x65, 0x78, 0x74, 0x22, 0x22, 0x0a, 0x0c, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 335 | 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 336 | 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x22, 0x27, 0x0a, 0x11, 0x50, 0x69, 0x6e, 0x67, 0x53, 337 | 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 338 | 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 339 | 0x22, 0x28, 0x0a, 0x12, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 340 | 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 341 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x32, 0xf2, 0x01, 0x0a, 0x0b, 0x50, 342 | 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x04, 0x50, 0x69, 343 | 0x6e, 0x67, 0x12, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 344 | 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 345 | 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 346 | 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x03, 0x90, 0x02, 0x01, 347 | 0x12, 0x44, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x1a, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 348 | 0x2e, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x71, 349 | 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 0x6e, 350 | 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x63, 0x68, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 351 | 0x65, 0x22, 0x03, 0x90, 0x02, 0x02, 0x12, 0x57, 0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 352 | 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 0x6e, 353 | 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 354 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 355 | 0x69, 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 356 | 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 357 | 0xac, 0x01, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x70, 0x69, 358 | 0x6e, 0x67, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 359 | 0x50, 0x01, 0x5a, 0x36, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x63, 360 | 0x6f, 0x6d, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 361 | 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x2f, 0x70, 0x69, 0x6e, 0x67, 362 | 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x69, 0x6e, 0x67, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x50, 0x58, 363 | 0xaa, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x2e, 0x56, 0x31, 364 | 0xca, 0x02, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x5c, 0x50, 0x69, 0x6e, 0x67, 0x5c, 0x56, 0x31, 365 | 0xe2, 0x02, 0x19, 0x41, 0x75, 0x74, 0x68, 0x6e, 0x5c, 0x50, 0x69, 0x6e, 0x67, 0x5c, 0x56, 0x31, 366 | 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0f, 0x41, 367 | 0x75, 0x74, 0x68, 0x6e, 0x3a, 0x3a, 0x50, 0x69, 0x6e, 0x67, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 368 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 369 | } 370 | 371 | var ( 372 | file_authn_ping_v1_ping_proto_rawDescOnce sync.Once 373 | file_authn_ping_v1_ping_proto_rawDescData = file_authn_ping_v1_ping_proto_rawDesc 374 | ) 375 | 376 | func file_authn_ping_v1_ping_proto_rawDescGZIP() []byte { 377 | file_authn_ping_v1_ping_proto_rawDescOnce.Do(func() { 378 | file_authn_ping_v1_ping_proto_rawDescData = protoimpl.X.CompressGZIP(file_authn_ping_v1_ping_proto_rawDescData) 379 | }) 380 | return file_authn_ping_v1_ping_proto_rawDescData 381 | } 382 | 383 | var file_authn_ping_v1_ping_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 384 | var file_authn_ping_v1_ping_proto_goTypes = []any{ 385 | (*PingRequest)(nil), // 0: authn.ping.v1.PingRequest 386 | (*PingResponse)(nil), // 1: authn.ping.v1.PingResponse 387 | (*EchoRequest)(nil), // 2: authn.ping.v1.EchoRequest 388 | (*EchoResponse)(nil), // 3: authn.ping.v1.EchoResponse 389 | (*PingStreamRequest)(nil), // 4: authn.ping.v1.PingStreamRequest 390 | (*PingStreamResponse)(nil), // 5: authn.ping.v1.PingStreamResponse 391 | } 392 | var file_authn_ping_v1_ping_proto_depIdxs = []int32{ 393 | 0, // 0: authn.ping.v1.PingService.Ping:input_type -> authn.ping.v1.PingRequest 394 | 2, // 1: authn.ping.v1.PingService.Echo:input_type -> authn.ping.v1.EchoRequest 395 | 4, // 2: authn.ping.v1.PingService.PingStream:input_type -> authn.ping.v1.PingStreamRequest 396 | 1, // 3: authn.ping.v1.PingService.Ping:output_type -> authn.ping.v1.PingResponse 397 | 3, // 4: authn.ping.v1.PingService.Echo:output_type -> authn.ping.v1.EchoResponse 398 | 5, // 5: authn.ping.v1.PingService.PingStream:output_type -> authn.ping.v1.PingStreamResponse 399 | 3, // [3:6] is the sub-list for method output_type 400 | 0, // [0:3] is the sub-list for method input_type 401 | 0, // [0:0] is the sub-list for extension type_name 402 | 0, // [0:0] is the sub-list for extension extendee 403 | 0, // [0:0] is the sub-list for field type_name 404 | } 405 | 406 | func init() { file_authn_ping_v1_ping_proto_init() } 407 | func file_authn_ping_v1_ping_proto_init() { 408 | if File_authn_ping_v1_ping_proto != nil { 409 | return 410 | } 411 | if !protoimpl.UnsafeEnabled { 412 | file_authn_ping_v1_ping_proto_msgTypes[0].Exporter = func(v any, i int) any { 413 | switch v := v.(*PingRequest); i { 414 | case 0: 415 | return &v.state 416 | case 1: 417 | return &v.sizeCache 418 | case 2: 419 | return &v.unknownFields 420 | default: 421 | return nil 422 | } 423 | } 424 | file_authn_ping_v1_ping_proto_msgTypes[1].Exporter = func(v any, i int) any { 425 | switch v := v.(*PingResponse); i { 426 | case 0: 427 | return &v.state 428 | case 1: 429 | return &v.sizeCache 430 | case 2: 431 | return &v.unknownFields 432 | default: 433 | return nil 434 | } 435 | } 436 | file_authn_ping_v1_ping_proto_msgTypes[2].Exporter = func(v any, i int) any { 437 | switch v := v.(*EchoRequest); i { 438 | case 0: 439 | return &v.state 440 | case 1: 441 | return &v.sizeCache 442 | case 2: 443 | return &v.unknownFields 444 | default: 445 | return nil 446 | } 447 | } 448 | file_authn_ping_v1_ping_proto_msgTypes[3].Exporter = func(v any, i int) any { 449 | switch v := v.(*EchoResponse); i { 450 | case 0: 451 | return &v.state 452 | case 1: 453 | return &v.sizeCache 454 | case 2: 455 | return &v.unknownFields 456 | default: 457 | return nil 458 | } 459 | } 460 | file_authn_ping_v1_ping_proto_msgTypes[4].Exporter = func(v any, i int) any { 461 | switch v := v.(*PingStreamRequest); i { 462 | case 0: 463 | return &v.state 464 | case 1: 465 | return &v.sizeCache 466 | case 2: 467 | return &v.unknownFields 468 | default: 469 | return nil 470 | } 471 | } 472 | file_authn_ping_v1_ping_proto_msgTypes[5].Exporter = func(v any, i int) any { 473 | switch v := v.(*PingStreamResponse); i { 474 | case 0: 475 | return &v.state 476 | case 1: 477 | return &v.sizeCache 478 | case 2: 479 | return &v.unknownFields 480 | default: 481 | return nil 482 | } 483 | } 484 | } 485 | type x struct{} 486 | out := protoimpl.TypeBuilder{ 487 | File: protoimpl.DescBuilder{ 488 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 489 | RawDescriptor: file_authn_ping_v1_ping_proto_rawDesc, 490 | NumEnums: 0, 491 | NumMessages: 6, 492 | NumExtensions: 0, 493 | NumServices: 1, 494 | }, 495 | GoTypes: file_authn_ping_v1_ping_proto_goTypes, 496 | DependencyIndexes: file_authn_ping_v1_ping_proto_depIdxs, 497 | MessageInfos: file_authn_ping_v1_ping_proto_msgTypes, 498 | }.Build() 499 | File_authn_ping_v1_ping_proto = out.File 500 | file_authn_ping_v1_ping_proto_rawDesc = nil 501 | file_authn_ping_v1_ping_proto_goTypes = nil 502 | file_authn_ping_v1_ping_proto_depIdxs = nil 503 | } 504 | -------------------------------------------------------------------------------- /internal/gen/authn/ping/v1/pingv1connect/ping.connect.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical location for this file is 16 | // https://github.com/connectrpc/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. 17 | 18 | // Code generated by protoc-gen-connect-go. DO NOT EDIT. 19 | // 20 | // Source: authn/ping/v1/ping.proto 21 | 22 | package pingv1connect 23 | 24 | import ( 25 | v1 "connectrpc.com/authn/internal/gen/authn/ping/v1" 26 | connect "connectrpc.com/connect" 27 | context "context" 28 | errors "errors" 29 | http "net/http" 30 | strings "strings" 31 | ) 32 | 33 | // This is a compile-time assertion to ensure that this generated file and the connect package are 34 | // compatible. If you get a compiler error that this constant is not defined, this code was 35 | // generated with a version of connect newer than the one compiled into your binary. You can fix the 36 | // problem by either regenerating this code with an older version of connect or updating the connect 37 | // version compiled into your binary. 38 | const _ = connect.IsAtLeastVersion1_13_0 39 | 40 | const ( 41 | // PingServiceName is the fully-qualified name of the PingService service. 42 | PingServiceName = "authn.ping.v1.PingService" 43 | ) 44 | 45 | // These constants are the fully-qualified names of the RPCs defined in this package. They're 46 | // exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. 47 | // 48 | // Note that these are different from the fully-qualified method names used by 49 | // google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to 50 | // reflection-formatted method names, remove the leading slash and convert the remaining slash to a 51 | // period. 52 | const ( 53 | // PingServicePingProcedure is the fully-qualified name of the PingService's Ping RPC. 54 | PingServicePingProcedure = "/authn.ping.v1.PingService/Ping" 55 | // PingServiceEchoProcedure is the fully-qualified name of the PingService's Echo RPC. 56 | PingServiceEchoProcedure = "/authn.ping.v1.PingService/Echo" 57 | // PingServicePingStreamProcedure is the fully-qualified name of the PingService's PingStream RPC. 58 | PingServicePingStreamProcedure = "/authn.ping.v1.PingService/PingStream" 59 | ) 60 | 61 | // These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. 62 | var ( 63 | pingServiceServiceDescriptor = v1.File_authn_ping_v1_ping_proto.Services().ByName("PingService") 64 | pingServicePingMethodDescriptor = pingServiceServiceDescriptor.Methods().ByName("Ping") 65 | pingServiceEchoMethodDescriptor = pingServiceServiceDescriptor.Methods().ByName("Echo") 66 | pingServicePingStreamMethodDescriptor = pingServiceServiceDescriptor.Methods().ByName("PingStream") 67 | ) 68 | 69 | // PingServiceClient is a client for the authn.ping.v1.PingService service. 70 | type PingServiceClient interface { 71 | // Ping is a unary RPC that returns the same text that was sent. 72 | Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) 73 | // Echo is a unary RPC that returns the same text that was sent. 74 | Echo(context.Context, *connect.Request[v1.EchoRequest]) (*connect.Response[v1.EchoResponse], error) 75 | // PingStream is a bidirectional stream of pings. 76 | PingStream(context.Context) *connect.BidiStreamForClient[v1.PingStreamRequest, v1.PingStreamResponse] 77 | } 78 | 79 | // NewPingServiceClient constructs a client for the authn.ping.v1.PingService service. By default, 80 | // it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and 81 | // sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() 82 | // or connect.WithGRPCWeb() options. 83 | // 84 | // The URL supplied here should be the base URL for the Connect or gRPC server (for example, 85 | // http://api.acme.com or https://acme.com/grpc). 86 | func NewPingServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) PingServiceClient { 87 | baseURL = strings.TrimRight(baseURL, "/") 88 | return &pingServiceClient{ 89 | ping: connect.NewClient[v1.PingRequest, v1.PingResponse]( 90 | httpClient, 91 | baseURL+PingServicePingProcedure, 92 | connect.WithSchema(pingServicePingMethodDescriptor), 93 | connect.WithIdempotency(connect.IdempotencyNoSideEffects), 94 | connect.WithClientOptions(opts...), 95 | ), 96 | echo: connect.NewClient[v1.EchoRequest, v1.EchoResponse]( 97 | httpClient, 98 | baseURL+PingServiceEchoProcedure, 99 | connect.WithSchema(pingServiceEchoMethodDescriptor), 100 | connect.WithIdempotency(connect.IdempotencyIdempotent), 101 | connect.WithClientOptions(opts...), 102 | ), 103 | pingStream: connect.NewClient[v1.PingStreamRequest, v1.PingStreamResponse]( 104 | httpClient, 105 | baseURL+PingServicePingStreamProcedure, 106 | connect.WithSchema(pingServicePingStreamMethodDescriptor), 107 | connect.WithClientOptions(opts...), 108 | ), 109 | } 110 | } 111 | 112 | // pingServiceClient implements PingServiceClient. 113 | type pingServiceClient struct { 114 | ping *connect.Client[v1.PingRequest, v1.PingResponse] 115 | echo *connect.Client[v1.EchoRequest, v1.EchoResponse] 116 | pingStream *connect.Client[v1.PingStreamRequest, v1.PingStreamResponse] 117 | } 118 | 119 | // Ping calls authn.ping.v1.PingService.Ping. 120 | func (c *pingServiceClient) Ping(ctx context.Context, req *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { 121 | return c.ping.CallUnary(ctx, req) 122 | } 123 | 124 | // Echo calls authn.ping.v1.PingService.Echo. 125 | func (c *pingServiceClient) Echo(ctx context.Context, req *connect.Request[v1.EchoRequest]) (*connect.Response[v1.EchoResponse], error) { 126 | return c.echo.CallUnary(ctx, req) 127 | } 128 | 129 | // PingStream calls authn.ping.v1.PingService.PingStream. 130 | func (c *pingServiceClient) PingStream(ctx context.Context) *connect.BidiStreamForClient[v1.PingStreamRequest, v1.PingStreamResponse] { 131 | return c.pingStream.CallBidiStream(ctx) 132 | } 133 | 134 | // PingServiceHandler is an implementation of the authn.ping.v1.PingService service. 135 | type PingServiceHandler interface { 136 | // Ping is a unary RPC that returns the same text that was sent. 137 | Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) 138 | // Echo is a unary RPC that returns the same text that was sent. 139 | Echo(context.Context, *connect.Request[v1.EchoRequest]) (*connect.Response[v1.EchoResponse], error) 140 | // PingStream is a bidirectional stream of pings. 141 | PingStream(context.Context, *connect.BidiStream[v1.PingStreamRequest, v1.PingStreamResponse]) error 142 | } 143 | 144 | // NewPingServiceHandler builds an HTTP handler from the service implementation. It returns the path 145 | // on which to mount the handler and the handler itself. 146 | // 147 | // By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf 148 | // and JSON codecs. They also support gzip compression. 149 | func NewPingServiceHandler(svc PingServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { 150 | pingServicePingHandler := connect.NewUnaryHandler( 151 | PingServicePingProcedure, 152 | svc.Ping, 153 | connect.WithSchema(pingServicePingMethodDescriptor), 154 | connect.WithIdempotency(connect.IdempotencyNoSideEffects), 155 | connect.WithHandlerOptions(opts...), 156 | ) 157 | pingServiceEchoHandler := connect.NewUnaryHandler( 158 | PingServiceEchoProcedure, 159 | svc.Echo, 160 | connect.WithSchema(pingServiceEchoMethodDescriptor), 161 | connect.WithIdempotency(connect.IdempotencyIdempotent), 162 | connect.WithHandlerOptions(opts...), 163 | ) 164 | pingServicePingStreamHandler := connect.NewBidiStreamHandler( 165 | PingServicePingStreamProcedure, 166 | svc.PingStream, 167 | connect.WithSchema(pingServicePingStreamMethodDescriptor), 168 | connect.WithHandlerOptions(opts...), 169 | ) 170 | return "/authn.ping.v1.PingService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 171 | switch r.URL.Path { 172 | case PingServicePingProcedure: 173 | pingServicePingHandler.ServeHTTP(w, r) 174 | case PingServiceEchoProcedure: 175 | pingServiceEchoHandler.ServeHTTP(w, r) 176 | case PingServicePingStreamProcedure: 177 | pingServicePingStreamHandler.ServeHTTP(w, r) 178 | default: 179 | http.NotFound(w, r) 180 | } 181 | }) 182 | } 183 | 184 | // UnimplementedPingServiceHandler returns CodeUnimplemented from all methods. 185 | type UnimplementedPingServiceHandler struct{} 186 | 187 | func (UnimplementedPingServiceHandler) Ping(context.Context, *connect.Request[v1.PingRequest]) (*connect.Response[v1.PingResponse], error) { 188 | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("authn.ping.v1.PingService.Ping is not implemented")) 189 | } 190 | 191 | func (UnimplementedPingServiceHandler) Echo(context.Context, *connect.Request[v1.EchoRequest]) (*connect.Response[v1.EchoResponse], error) { 192 | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("authn.ping.v1.PingService.Echo is not implemented")) 193 | } 194 | 195 | func (UnimplementedPingServiceHandler) PingStream(context.Context, *connect.BidiStream[v1.PingStreamRequest, v1.PingStreamResponse]) error { 196 | return connect.NewError(connect.CodeUnimplemented, errors.New("authn.ping.v1.PingService.PingStream is not implemented")) 197 | } 198 | -------------------------------------------------------------------------------- /internal/proto/authn/ping/v1/ping.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2023-2024 Buf Technologies, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The canonical location for this file is 16 | // https://github.com/connectrpc/authn-go/blob/main/internal/proto/authn/ping/v1/ping.proto. 17 | syntax = "proto3"; 18 | 19 | package authn.ping.v1; 20 | 21 | message PingRequest { 22 | string text = 1; 23 | } 24 | 25 | message PingResponse { 26 | string text = 1; 27 | } 28 | 29 | message EchoRequest { 30 | string text = 1; 31 | } 32 | 33 | message EchoResponse { 34 | string text = 1; 35 | } 36 | 37 | message PingStreamRequest { 38 | string text = 1; 39 | } 40 | 41 | message PingStreamResponse { 42 | string text = 1; 43 | } 44 | 45 | service PingService { 46 | // Ping is a unary RPC that returns the same text that was sent. 47 | rpc Ping(PingRequest) returns (PingResponse) { 48 | option idempotency_level = NO_SIDE_EFFECTS; 49 | } 50 | // Echo is a unary RPC that returns the same text that was sent. 51 | rpc Echo(EchoRequest) returns (EchoResponse) { 52 | option idempotency_level = IDEMPOTENT; 53 | } 54 | // PingStream is a bidirectional stream of pings. 55 | rpc PingStream(stream PingStreamRequest) returns (stream PingStreamResponse) {} 56 | } 57 | --------------------------------------------------------------------------------