diff --git a/.forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml similarity index 99% rename from .forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml rename to .github/ISSUE_TEMPLATE/bug_report.yaml index d024341..92465f9 100644 --- a/.forgejo.bak/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -2,7 +2,6 @@ name: Bug Report description: "Create a bug report to help us improve ZITADEL. Click [here](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#product-management) to see how we process your issue." title: "[Bug]: " labels: ["bug"] -type: Bug body: - type: markdown attributes: diff --git a/.forgejo.bak/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml similarity index 100% rename from .forgejo.bak/ISSUE_TEMPLATE/config.yml rename to .github/ISSUE_TEMPLATE/config.yml diff --git a/.forgejo.bak/ISSUE_TEMPLATE/docs.yaml b/.github/ISSUE_TEMPLATE/docs.yaml similarity index 98% rename from .forgejo.bak/ISSUE_TEMPLATE/docs.yaml rename to .github/ISSUE_TEMPLATE/docs.yaml index d3f82b9..04c1c0c 100644 --- a/.forgejo.bak/ISSUE_TEMPLATE/docs.yaml +++ b/.github/ISSUE_TEMPLATE/docs.yaml @@ -1,7 +1,6 @@ name: 📄 Documentation description: Create an issue for missing or wrong documentation. labels: ["docs"] -type: task body: - type: markdown attributes: diff --git a/.forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/improvement.yaml similarity index 92% rename from .forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml rename to .github/ISSUE_TEMPLATE/improvement.yaml index ef2103e..2e2ddf4 100644 --- a/.forgejo.bak/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/improvement.yaml @@ -1,12 +1,11 @@ name: 🛠️ Improvement description: "Create an new issue for an improvment in ZITADEL" -labels: ["enhancement"] -type: enhancement +labels: ["improvement"] body: - type: markdown attributes: value: | - Thanks for taking the time to fill out this proposal / feature reqeust + Thanks for taking the time to fill out this improvement request - type: checkboxes id: preflight attributes: diff --git a/.github/ISSUE_TEMPLATE/proposal.yaml b/.github/ISSUE_TEMPLATE/proposal.yaml new file mode 100644 index 0000000..af7acd5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/proposal.yaml @@ -0,0 +1,44 @@ +name: 💡 Proposal / Feature request +description: "Create an issue for a feature request/proposal." +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this proposal / feature reqeust + - type: checkboxes + id: preflight + attributes: + label: Preflight Checklist + options: + - label: + I could not find a solution in the existing issues, docs, nor discussions + required: true + - label: + I have joined the [ZITADEL chat](https://zitadel.com/chat) + - type: textarea + id: problem + attributes: + label: Describe your problem + description: Please describe your problem this proposal / feature is supposed to solve. + placeholder: Describe the problem you have. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Describe your ideal solution + description: Which solution do you propose? + placeholder: As a [type of user], I want [some goal] so that [some reason]. + validations: + required: true + - type: input + id: version + attributes: + label: Version + description: Which version of the OIDC Library are you using. + - type: textarea + id: additional + attributes: + label: Additional Context + description: Please add any other infos that could be useful. diff --git a/.forgejo.bak/dependabot.yml b/.github/dependabot.yml similarity index 100% rename from .forgejo.bak/dependabot.yml rename to .github/dependabot.yml diff --git a/.forgejo.bak/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .forgejo.bak/pull_request_template.md rename to .github/pull_request_template.md diff --git a/.forgejo.bak/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml similarity index 100% rename from .forgejo.bak/workflows/codeql-analysis.yml rename to .github/workflows/codeql-analysis.yml diff --git a/.forgejo.bak/workflows/issue.yml b/.github/workflows/issue.yml similarity index 94% rename from .forgejo.bak/workflows/issue.yml rename to .github/workflows/issue.yml index 480c339..d328058 100644 --- a/.forgejo.bak/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: add issue - uses: actions/add-to-project@v1.0.2 + uses: actions/add-to-project@v1 if: ${{ github.event_name == 'issues' }} with: # You can target a repository in a different organization @@ -28,7 +28,7 @@ jobs: username: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }} - name: add pr - uses: actions/add-to-project@v1.0.2 + uses: actions/add-to-project@v1 if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}} with: # You can target a repository in a different organization diff --git a/.forgejo.bak/workflows/release.yml b/.github/workflows/release.yml similarity index 89% rename from .forgejo.bak/workflows/release.yml rename to .github/workflows/release.yml index 00063e4..202596f 100644 --- a/.forgejo.bak/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,11 +14,11 @@ on: jobs: test: - runs-on: ubuntu-24.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: - go: ['1.23', '1.24'] + go: ['1.21', '1.22'] name: Go ${{ matrix.go }} test steps: - uses: actions/checkout@v4 @@ -27,12 +27,12 @@ jobs: with: go-version: ${{ matrix.go }} - run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/... - - uses: codecov/codecov-action@v5.4.3 + - uses: codecov/codecov-action@v4.1.1 with: file: ./profile.cov name: codecov-go release: - runs-on: ubuntu-24.04 + runs-on: ubuntu-20.04 needs: [test] if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }} env: diff --git a/README.md b/README.md index bc346f5..f76c2b2 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Release](https://github.com/zitadel/oidc/workflows/Release/badge.svg)](https://github.com/zitadel/oidc/actions) -[![Go Reference](https://pkg.go.dev/badge/github.com/zitadel/oidc/v3.svg)](https://pkg.go.dev/github.com/zitadel/oidc/v3) +[![Go Reference](https://pkg.go.dev/badge/github.com/zitadel/oidc/v4.svg)](https://pkg.go.dev/github.com/zitadel/oidc/v4) [![license](https://badgen.net/github/license/zitadel/oidc/)](https://github.com/zitadel/oidc/blob/master/LICENSE) [![release](https://badgen.net/github/release/zitadel/oidc/stable)](https://github.com/zitadel/oidc/releases) -[![Go Report Card](https://goreportcard.com/badge/github.com/zitadel/oidc/v3)](https://goreportcard.com/report/github.com/zitadel/oidc/v3) +[![Go Report Card](https://goreportcard.com/badge/github.com/zitadel/oidc/v4)](https://goreportcard.com/report/github.com/zitadel/oidc/v4) [![codecov](https://codecov.io/gh/zitadel/oidc/branch/main/graph/badge.svg)](https://codecov.io/gh/zitadel/oidc) [![openid_certified](https://cloud.githubusercontent.com/assets/1454075/7611268/4d19de32-f97b-11e4-895b-31b2455a7ca6.png)](https://openid.net/certification/) @@ -21,10 +21,9 @@ Whenever possible we tried to reuse / extend existing packages like `OAuth2 for ## Basic Overview The most important packages of the library: -
 /pkg
-    /client            clients using the OP for retrieving, exchanging and verifying tokens
+    /client            clients using the OP for retrieving, exchanging and verifying tokens       
         /rp            definition and implementation of an OIDC Relying Party (client)
         /rs            definition and implementation of an OAuth Resource Server (API)
     /op                definition and implementation of an OIDC OpenID Provider (server)
@@ -38,6 +37,7 @@ The most important packages of the library:
     /server            examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
 
+ ### Semver This package uses [semver](https://semver.org/) for [releases](https://github.com/zitadel/oidc/releases). Major releases ship breaking changes. Starting with the `v2` to `v3` increment we provide an [upgrade guide](UPGRADING.md) to ease migration to a newer version. @@ -49,90 +49,54 @@ Check the `/example` folder where example code for different scenarios is locate ```bash # start oidc op server # oidc discovery http://localhost:9998/.well-known/openid-configuration -go run github.com/zitadel/oidc/v3/example/server +go run github.com/zitadel/oidc/v4/example/server # start oidc web client (in a new terminal) -CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app +CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v4/example/client/app ``` - open http://localhost:9999/login in your browser -- you will be redirected to op server and the login UI +- you will be redirected to op server and the login UI - login with user `test-user@localhost` and password `verysecure` - the OP will redirect you to the client app, which displays the user info for the dynamic issuer, just start it with: - ```bash -go run github.com/zitadel/oidc/v3/example/server/dynamic -``` - +go run github.com/zitadel/oidc/v4/example/server/dynamic +``` the oidc web client above will still work, but if you add `oidc.local` (pointing to 127.0.0.1) in your hosts file you can also start it with: - ```bash -CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v3/example/client/app +CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid profile" PORT=9999 go run github.com/zitadel/oidc/v4/example/client/app ``` > Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`) -### Server configuration - -Example server allows extra configuration using environment variables and could be used for end to -end testing of your services. - -| Name | Format | Description | -| ------------ | -------------------------------- | ------------------------------------- | -| PORT | Number between 1 and 65535 | OIDC listen port | -| REDIRECT_URI | Comma-separated URIs | List of allowed redirect URIs | -| USERS_FILE | Path to json in local filesystem | Users with their data and credentials | - -Here is json equivalent for one of the default users - -```json -{ - "id2": { - "ID": "id2", - "Username": "test-user2", - "Password": "verysecure", - "FirstName": "Test", - "LastName": "User2", - "Email": "test-user2@zitadel.ch", - "EmailVerified": true, - "Phone": "", - "PhoneVerified": false, - "PreferredLanguage": "DE", - "IsAdmin": false - } -} -``` - ## Features -| | Relying party | OpenID Provider | Specification | -| -------------------- | ------------- | --------------- | -------------------------------------------- | -| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] | -| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] | -| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] | -| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] | -| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] | -| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 | -| JWT Profile | yes | yes | [RFC 7523][7] | -| PKCE | yes | yes | [RFC 7636][8] | -| Token Exchange | yes | yes | [RFC 8693][9] | -| Device Authorization | yes | yes | [RFC 8628][10] | -| mTLS | not yet | not yet | [RFC 8705][11] | -| Back-Channel Logout | not yet | yes | OpenID Connect [Back-Channel Logout][12] 1.0 | +| | Relying party | OpenID Provider | Specification | +| -------------------- | ------------- | --------------- | ----------------------------------------- | +| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] | +| Implicit Flow | no[^1] | yes | OpenID Connect Core 1.0, [Section 3.2][2] | +| Hybrid Flow | no | not yet | OpenID Connect Core 1.0, [Section 3.3][3] | +| Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] | +| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] | +| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 | +| JWT Profile | yes | yes | [RFC 7523][7] | +| PKCE | yes | yes | [RFC 7636][8] | +| Token Exchange | yes | yes | [RFC 8693][9] | +| Device Authorization | yes | yes | [RFC 8628][10] | +| mTLS | not yet | not yet | [RFC 8705][11] | -[1]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth "3.1. Authentication using the Authorization Code Flow" -[2]: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth "3.2. Authentication using the Implicit Flow" -[3]: https://openid.net/specs/openid-connect-core-1_0.html#HybridFlowAuth "3.3. Authentication using the Hybrid Flow" -[4]: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication "9. Client Authentication" -[5]: https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens "12. Using Refresh Tokens" -[6]: https://openid.net/specs/openid-connect-discovery-1_0.html "OpenID Connect Discovery 1.0 incorporating errata set 1" -[7]: https://www.rfc-editor.org/rfc/rfc7523.html "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants" -[8]: https://www.rfc-editor.org/rfc/rfc7636.html "Proof Key for Code Exchange by OAuth Public Clients" -[9]: https://www.rfc-editor.org/rfc/rfc8693.html "OAuth 2.0 Token Exchange" -[10]: https://www.rfc-editor.org/rfc/rfc8628.html "OAuth 2.0 Device Authorization Grant" -[11]: https://www.rfc-editor.org/rfc/rfc8705.html "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens" -[12]: https://openid.net/specs/openid-connect-backchannel-1_0.html "OpenID Connect Back-Channel Logout 1.0 incorporating errata set 1" +[1]: "3.1. Authentication using the Authorization Code Flow" +[2]: "3.2. Authentication using the Implicit Flow" +[3]: "3.3. Authentication using the Hybrid Flow" +[4]: "9. Client Authentication" +[5]: "12. Using Refresh Tokens" +[6]: "OpenID Connect Discovery 1.0 incorporating errata set 1" +[7]: "JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants" +[8]: "Proof Key for Code Exchange by OAuth Public Clients" +[9]: "OAuth 2.0 Token Exchange" +[10]: "OAuth 2.0 Device Authorization Grant" +[11]: "OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens" ## Contributors @@ -151,14 +115,14 @@ For your convenience you can find the relevant guides linked below. ## Supported Go Versions -For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:). +For security reasons, we only support and recommend the use of one of the latest two Go versions (:white_check_mark:). Versions that also build are marked with :warning:. | Version | Supported | | ------- | ------------------ | -| <1.23 | :x: | -| 1.23 | :white_check_mark: | -| 1.24 | :white_check_mark: | +| <1.21 | :x: | +| 1.21 | :white_check_mark: | +| 1.22 | :white_check_mark: | ## Why another library @@ -189,4 +153,5 @@ Unless required by applicable law or agreed to in writing, software distributed AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + [^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892 diff --git a/example/client/api/api.go b/example/client/api/api.go index 69f9466..14e18f7 100644 --- a/example/client/api/api.go +++ b/example/client/api/api.go @@ -13,8 +13,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/sirupsen/logrus" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client/rs" + "github.com/zitadel/oidc/v4/pkg/oidc" ) const ( diff --git a/example/client/app/app.go b/example/client/app/app.go index 90b1969..2e9b62b 100644 --- a/example/client/app/app.go +++ b/example/client/app/app.go @@ -14,10 +14,10 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/logging" + "github.com/zitadel/oidc/v4/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) var ( @@ -56,7 +56,6 @@ func main() { rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), rp.WithHTTPClient(client), rp.WithLogger(logger), - rp.WithSigningAlgsFromDiscovery(), } if clientSecret == "" { options = append(options, rp.WithPKCE(cookieHandler)) @@ -109,7 +108,6 @@ func main() { http.Error(w, err.Error(), http.StatusInternalServerError) return } - w.Header().Set("content-type", "application/json") w.Write(data) } diff --git a/example/client/device/device.go b/example/client/device/device.go index 33bc570..065b289 100644 --- a/example/client/device/device.go +++ b/example/client/device/device.go @@ -45,8 +45,8 @@ import ( "github.com/sirupsen/logrus" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" + "github.com/zitadel/oidc/v4/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v4/pkg/http" ) var ( diff --git a/example/client/github/github.go b/example/client/github/github.go index f6c536b..88207a9 100644 --- a/example/client/github/github.go +++ b/example/client/github/github.go @@ -10,10 +10,10 @@ import ( "golang.org/x/oauth2" githubOAuth "golang.org/x/oauth2/github" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp/cli" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client/rp" + "github.com/zitadel/oidc/v4/pkg/client/rp/cli" + "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) var ( diff --git a/example/client/service/service.go b/example/client/service/service.go index a88ab2f..acfe7ac 100644 --- a/example/client/service/service.go +++ b/example/client/service/service.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/oauth2" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/profile" + "github.com/zitadel/oidc/v4/pkg/client/profile" ) var client = http.DefaultClient diff --git a/example/server/config/config.go b/example/server/config/config.go deleted file mode 100644 index 96837d4..0000000 --- a/example/server/config/config.go +++ /dev/null @@ -1,40 +0,0 @@ -package config - -import ( - "os" - "strings" -) - -const ( - // default port for the http server to run - DefaultIssuerPort = "9998" -) - -type Config struct { - Port string - RedirectURI []string - UsersFile string -} - -// FromEnvVars loads configuration parameters from environment variables. -// If there is no such variable defined, then use default values. -func FromEnvVars(defaults *Config) *Config { - if defaults == nil { - defaults = &Config{} - } - cfg := &Config{ - Port: defaults.Port, - RedirectURI: defaults.RedirectURI, - UsersFile: defaults.UsersFile, - } - if value, ok := os.LookupEnv("PORT"); ok { - cfg.Port = value - } - if value, ok := os.LookupEnv("USERS_FILE"); ok { - cfg.UsersFile = value - } - if value, ok := os.LookupEnv("REDIRECT_URI"); ok { - cfg.RedirectURI = strings.Split(value, ",") - } - return cfg -} diff --git a/example/server/config/config_test.go b/example/server/config/config_test.go deleted file mode 100644 index 3b73c0b..0000000 --- a/example/server/config/config_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package config - -import ( - "fmt" - "os" - "testing" -) - -func TestFromEnvVars(t *testing.T) { - - for _, tc := range []struct { - name string - env map[string]string - defaults *Config - want *Config - }{ - { - name: "no vars, no default values", - env: map[string]string{}, - want: &Config{}, - }, - { - name: "no vars, only defaults", - env: map[string]string{}, - defaults: &Config{ - Port: "6666", - UsersFile: "/default/user/path", - RedirectURI: []string{"re", "direct", "uris"}, - }, - want: &Config{ - Port: "6666", - UsersFile: "/default/user/path", - RedirectURI: []string{"re", "direct", "uris"}, - }, - }, - { - name: "overriding default values", - env: map[string]string{ - "PORT": "1234", - "USERS_FILE": "/path/to/users", - "REDIRECT_URI": "http://redirect/redirect", - }, - defaults: &Config{ - Port: "6666", - UsersFile: "/default/user/path", - RedirectURI: []string{"re", "direct", "uris"}, - }, - want: &Config{ - Port: "1234", - UsersFile: "/path/to/users", - RedirectURI: []string{"http://redirect/redirect"}, - }, - }, - { - name: "multiple redirect uris", - env: map[string]string{ - "REDIRECT_URI": "http://host_1,http://host_2,http://host_3", - }, - want: &Config{ - RedirectURI: []string{ - "http://host_1", "http://host_2", "http://host_3", - }, - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - os.Clearenv() - for k, v := range tc.env { - os.Setenv(k, v) - } - cfg := FromEnvVars(tc.defaults) - if fmt.Sprint(cfg) != fmt.Sprint(tc.want) { - t.Errorf("Expected FromEnvVars()=%q, but got %q", tc.want, cfg) - } - }) - } -} diff --git a/example/server/dynamic/login.go b/example/server/dynamic/login.go index 05f0e34..5985f87 100644 --- a/example/server/dynamic/login.go +++ b/example/server/dynamic/login.go @@ -8,7 +8,7 @@ import ( "github.com/go-chi/chi/v5" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/op" ) const ( diff --git a/example/server/dynamic/op.go b/example/server/dynamic/op.go index 2c00e41..d27d386 100644 --- a/example/server/dynamic/op.go +++ b/example/server/dynamic/op.go @@ -10,8 +10,8 @@ import ( "github.com/go-chi/chi/v5" "golang.org/x/text/language" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/example/server/storage" + "github.com/zitadel/oidc/v4/pkg/op" ) const ( diff --git a/example/server/exampleop/device.go b/example/server/exampleop/device.go index 99505e4..667aee7 100644 --- a/example/server/exampleop/device.go +++ b/example/server/exampleop/device.go @@ -8,10 +8,10 @@ import ( "net/http" "net/url" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/go-chi/chi/v5" "github.com/gorilla/securecookie" "github.com/sirupsen/logrus" + "github.com/zitadel/oidc/v4/pkg/op" ) type deviceAuthenticate interface { diff --git a/example/server/exampleop/login.go b/example/server/exampleop/login.go index 77a6189..9d2bbb8 100644 --- a/example/server/exampleop/login.go +++ b/example/server/exampleop/login.go @@ -5,8 +5,8 @@ import ( "fmt" "net/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/go-chi/chi/v5" + "github.com/zitadel/oidc/v4/pkg/op" ) type login struct { diff --git a/example/server/exampleop/op.go b/example/server/exampleop/op.go index e12c755..26405ca 100644 --- a/example/server/exampleop/op.go +++ b/example/server/exampleop/op.go @@ -12,13 +12,22 @@ import ( "github.com/zitadel/logging" "golang.org/x/text/language" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/example/server/storage" + "github.com/zitadel/oidc/v4/pkg/op" ) const ( pathLoggedOut = "/logged-out" ) +func init() { + storage.RegisterClients( + storage.NativeClient("native"), + storage.WebClient("web", "secret"), + storage.WebClient("api", "secret"), + ) +} + type Storage interface { op.Storage authenticate @@ -47,7 +56,7 @@ func SetupServer(issuer string, storage Storage, logger *slog.Logger, wrapServer // for simplicity, we provide a very small default page for users who have signed out router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("signed out successfully")) - // no need to check/log error, this will be handled by the middleware. + // no need to check/log error, this will be handeled by the middleware. }) // creation of the OpenIDProvider with the just created in-memory Storage @@ -71,7 +80,7 @@ func SetupServer(issuer string, storage Storage, logger *slog.Logger, wrapServer handler := http.Handler(provider) if wrapServer { - handler = op.RegisterLegacyServer(op.NewLegacyServer(provider, *op.DefaultEndpoints), op.AuthorizeCallbackHandler(provider)) + handler = op.RegisterLegacyServer(op.NewLegacyServer(provider, *op.DefaultEndpoints)) } // we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration) diff --git a/example/server/main.go b/example/server/main.go index 5bdbb05..c5d3610 100644 --- a/example/server/main.go +++ b/example/server/main.go @@ -6,53 +6,36 @@ import ( "net/http" "os" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/config" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" + "github.com/zitadel/oidc/v4/example/server/exampleop" + "github.com/zitadel/oidc/v4/example/server/storage" ) -func getUserStore(cfg *config.Config) (storage.UserStore, error) { - if cfg.UsersFile == "" { - return storage.NewUserStore(fmt.Sprintf("http://localhost:%s/", cfg.Port)), nil - } - return storage.StoreFromFile(cfg.UsersFile) -} - func main() { - cfg := config.FromEnvVars(&config.Config{Port: "9998"}) + //we will run on :9998 + port := "9998" + //which gives us the issuer: http://localhost:9998/ + issuer := fmt.Sprintf("http://localhost:%s/", port) + + // the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations + // this might be the layer for accessing your database + // in this example it will be handled in-memory + storage := storage.NewStorage(storage.NewUserStore(issuer)) + logger := slog.New( slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ AddSource: true, Level: slog.LevelDebug, }), ) - - //which gives us the issuer: http://localhost:9998/ - issuer := fmt.Sprintf("http://localhost:%s/", cfg.Port) - - storage.RegisterClients( - storage.NativeClient("native", cfg.RedirectURI...), - storage.WebClient("web", "secret", cfg.RedirectURI...), - storage.WebClient("api", "secret", cfg.RedirectURI...), - ) - - // the OpenIDProvider interface needs a Storage interface handling various checks and state manipulations - // this might be the layer for accessing your database - // in this example it will be handled in-memory - store, err := getUserStore(cfg) - if err != nil { - logger.Error("cannot create UserStore", "error", err) - os.Exit(1) - } - storage := storage.NewStorage(store) router := exampleop.SetupServer(issuer, storage, logger, false) server := &http.Server{ - Addr: ":" + cfg.Port, + Addr: ":" + port, Handler: router, } - logger.Info("server listening, press ctrl+c to stop", "addr", issuer) - if server.ListenAndServe() != http.ErrServerClosed { + logger.Info("server listening, press ctrl+c to stop", "addr", fmt.Sprintf("http://localhost:%s/", port)) + err := server.ListenAndServe() + if err != http.ErrServerClosed { logger.Error("server terminated", "error", err) os.Exit(1) } diff --git a/example/server/storage/client.go b/example/server/storage/client.go index 2b836c0..b16ad84 100644 --- a/example/server/storage/client.go +++ b/example/server/storage/client.go @@ -3,8 +3,8 @@ package storage import ( "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) var ( diff --git a/example/server/storage/oidc.go b/example/server/storage/oidc.go index 9c7f544..ce5d0b6 100644 --- a/example/server/storage/oidc.go +++ b/example/server/storage/oidc.go @@ -6,8 +6,8 @@ import ( "golang.org/x/text/language" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) const ( @@ -121,7 +121,7 @@ func (a *AuthRequest) Done() bool { } func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string { - prompts := make([]string, 0, len(oidcPrompt)) + prompts := make([]string, len(oidcPrompt)) for _, oidcPrompt := range oidcPrompt { switch oidcPrompt { case oidc.PromptNone, @@ -164,15 +164,6 @@ func authRequestToInternal(authReq *oidc.AuthRequest, userID string) *AuthReques } } -type AuthRequestWithSessionState struct { - *AuthRequest - SessionState string -} - -func (a *AuthRequestWithSessionState) GetSessionState() string { - return a.SessionState -} - type OIDCCodeChallenge struct { Challenge string Method string diff --git a/example/server/storage/storage.go b/example/server/storage/storage.go index d4315c6..3b826ac 100644 --- a/example/server/storage/storage.go +++ b/example/server/storage/storage.go @@ -11,11 +11,11 @@ import ( "sync" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/google/uuid" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) // serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant @@ -151,9 +151,6 @@ func (s *Storage) CheckUsernamePassword(username, password, id string) error { // in this example we'll simply check the username / password and set a boolean to true // therefore we will also just check this boolean if the request / login has been finished request.done = true - - request.authTime = time.Now() - return nil } return fmt.Errorf("username or password wrong") @@ -298,19 +295,15 @@ func (s *Storage) CreateAccessAndRefreshTokens(ctx context.Context, request op.T // if we get here, the currentRefreshToken was not empty, so the call is a refresh token request // we therefore will have to check the currentRefreshToken and renew the refresh token - - newRefreshToken = uuid.NewString() - - accessToken, err := s.accessToken(applicationID, newRefreshToken, request.GetSubject(), request.GetAudience(), request.GetScopes()) + refreshToken, refreshTokenID, err := s.renewRefreshToken(currentRefreshToken) if err != nil { return "", "", time.Time{}, err } - - if err := s.renewRefreshToken(currentRefreshToken, newRefreshToken, accessToken.ID); err != nil { + accessToken, err := s.accessToken(applicationID, refreshTokenID, request.GetSubject(), request.GetAudience(), request.GetScopes()) + if err != nil { return "", "", time.Time{}, err } - - return accessToken.ID, newRefreshToken, accessToken.Expiration, nil + return accessToken.ID, refreshToken, accessToken.Expiration, nil } func (s *Storage) exchangeRefreshToken(ctx context.Context, request op.TokenExchangeRequest) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) { @@ -392,9 +385,14 @@ func (s *Storage) RevokeToken(ctx context.Context, tokenIDOrToken string, userID if refreshToken.ApplicationID != clientID { return oidc.ErrInvalidClient().WithDescription("token was not issued for this client") } - delete(s.refreshTokens, refreshToken.ID) // if it is a refresh token, you will have to remove the access token as well - delete(s.tokens, refreshToken.AccessToken) + delete(s.refreshTokens, refreshToken.ID) + for _, accessToken := range s.tokens { + if accessToken.RefreshTokenID == refreshToken.ID { + delete(s.tokens, accessToken.ID) + return nil + } + } return nil } @@ -490,9 +488,6 @@ func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserI // return err // } //} - if token.Expiration.Before(time.Now()) { - return fmt.Errorf("token is expired") - } return s.setUserinfo(ctx, userinfo, token.Subject, token.ApplicationID, token.Scopes) } @@ -599,41 +594,33 @@ func (s *Storage) createRefreshToken(accessToken *Token, amr []string, authTime Audience: accessToken.Audience, Expiration: time.Now().Add(5 * time.Hour), Scopes: accessToken.Scopes, - AccessToken: accessToken.ID, } s.refreshTokens[token.ID] = token return token.Token, nil } // renewRefreshToken checks the provided refresh_token and creates a new one based on the current -// -// [Refresh Token Rotation] is implemented. -// -// [Refresh Token Rotation]: https://www.rfc-editor.org/rfc/rfc6819#section-5.2.2.3 -func (s *Storage) renewRefreshToken(currentRefreshToken, newRefreshToken, newAccessToken string) error { +func (s *Storage) renewRefreshToken(currentRefreshToken string) (string, string, error) { s.lock.Lock() defer s.lock.Unlock() refreshToken, ok := s.refreshTokens[currentRefreshToken] if !ok { - return fmt.Errorf("invalid refresh token") + return "", "", fmt.Errorf("invalid refresh token") } - // deletes the refresh token + // deletes the refresh token and all access tokens which were issued based on this refresh token delete(s.refreshTokens, currentRefreshToken) - - // delete the access token which was issued based on this refresh token - delete(s.tokens, refreshToken.AccessToken) - - if refreshToken.Expiration.Before(time.Now()) { - return fmt.Errorf("expired refresh token") + for _, token := range s.tokens { + if token.RefreshTokenID == currentRefreshToken { + delete(s.tokens, token.ID) + break + } } - // creates a new refresh token based on the current one - refreshToken.Token = newRefreshToken - refreshToken.ID = newRefreshToken - refreshToken.Expiration = time.Now().Add(5 * time.Hour) - refreshToken.AccessToken = newAccessToken - s.refreshTokens[newRefreshToken] = refreshToken - return nil + token := uuid.NewString() + refreshToken.Token = token + refreshToken.ID = token + s.refreshTokens[token] = refreshToken + return token, refreshToken.ID, nil } // accessToken will store an access_token in-memory based on the provided information diff --git a/example/server/storage/storage_dynamic.go b/example/server/storage/storage_dynamic.go index 765d29a..f15ab23 100644 --- a/example/server/storage/storage_dynamic.go +++ b/example/server/storage/storage_dynamic.go @@ -4,10 +4,10 @@ import ( "context" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) type multiStorage struct { diff --git a/example/server/storage/token.go b/example/server/storage/token.go index beab38c..ad907e3 100644 --- a/example/server/storage/token.go +++ b/example/server/storage/token.go @@ -22,5 +22,4 @@ type RefreshToken struct { ApplicationID string Expiration time.Time Scopes []string - AccessToken string // Token.ID } diff --git a/example/server/storage/user.go b/example/server/storage/user.go index ed8cdfa..173daef 100644 --- a/example/server/storage/user.go +++ b/example/server/storage/user.go @@ -2,8 +2,6 @@ package storage import ( "crypto/rsa" - "encoding/json" - "os" "strings" "golang.org/x/text/language" @@ -37,18 +35,6 @@ type userStore struct { users map[string]*User } -func StoreFromFile(path string) (UserStore, error) { - users := map[string]*User{} - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - if err := json.Unmarshal(data, &users); err != nil { - return nil, err - } - return userStore{users}, nil -} - func NewUserStore(issuer string) UserStore { hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0] return userStore{ diff --git a/example/server/storage/user_test.go b/example/server/storage/user_test.go deleted file mode 100644 index c2e2212..0000000 --- a/example/server/storage/user_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package storage - -import ( - "os" - "path" - "reflect" - "testing" - - "golang.org/x/text/language" -) - -func TestStoreFromFile(t *testing.T) { - for _, tc := range []struct { - name string - pathToFile string - content string - want UserStore - wantErr bool - }{ - { - name: "normal user file", - pathToFile: "userfile.json", - content: `{ - "id1": { - "ID": "id1", - "EmailVerified": true, - "PreferredLanguage": "DE" - } - }`, - want: userStore{map[string]*User{ - "id1": { - ID: "id1", - EmailVerified: true, - PreferredLanguage: language.German, - }, - }}, - }, - { - name: "malformed file", - pathToFile: "whatever", - content: "not a json just a text", - wantErr: true, - }, - { - name: "not existing file", - pathToFile: "what/ever/file", - wantErr: true, - }, - } { - t.Run(tc.name, func(t *testing.T) { - actualPath := path.Join(t.TempDir(), tc.pathToFile) - - if tc.content != "" && tc.pathToFile != "" { - if err := os.WriteFile(actualPath, []byte(tc.content), 0666); err != nil { - t.Fatalf("cannot create file with test content: %q", tc.content) - } - } - result, err := StoreFromFile(actualPath) - if err != nil && !tc.wantErr { - t.Errorf("StoreFromFile(%q) returned unexpected error %q", tc.pathToFile, err) - } else if err == nil && tc.wantErr { - t.Errorf("StoreFromFile(%q) did not return an expected error", tc.pathToFile) - } - if !tc.wantErr && !reflect.DeepEqual(tc.want, result.(userStore)) { - t.Errorf("expected StoreFromFile(%q) = %v, but got %v", - tc.pathToFile, tc.want, result) - } - }) - } -} diff --git a/go.mod b/go.mod index a0f42c4..58c20dc 100644 --- a/go.mod +++ b/go.mod @@ -1,40 +1,41 @@ -module git.christmann.info/LARA/zitadel-oidc/v3 +module github.com/zitadel/oidc/v4 -go 1.23.7 - -toolchain go1.24.1 +go 1.21 require ( - github.com/bmatcuk/doublestar/v4 v4.8.1 - github.com/go-chi/chi/v5 v5.2.1 - github.com/go-jose/go-jose/v4 v4.0.5 + github.com/bmatcuk/doublestar/v4 v4.6.1 + github.com/go-chi/chi/v5 v5.0.12 + github.com/go-jose/go-jose/v3 v3.0.3 github.com/golang/mock v1.6.0 github.com/google/go-github/v31 v31.0.0 github.com/google/uuid v1.6.0 github.com/gorilla/securecookie v1.1.2 - github.com/jeremija/gosubmit v0.2.8 + github.com/jeremija/gosubmit v0.2.7 github.com/muhlemmer/gu v0.3.1 github.com/muhlemmer/httpforwarded v0.1.0 - github.com/rs/cors v1.11.1 + github.com/rs/cors v1.10.1 github.com/sirupsen/logrus v1.9.3 - github.com/stretchr/testify v1.10.0 - github.com/zitadel/logging v0.6.2 - github.com/zitadel/schema v1.3.1 - go.opentelemetry.io/otel v1.29.0 - golang.org/x/oauth2 v0.30.0 - golang.org/x/text v0.26.0 + github.com/stretchr/testify v1.9.0 + github.com/zitadel/logging v0.6.0 + github.com/zitadel/schema v1.3.0 + go.opentelemetry.io/otel v1.24.0 + golang.org/x/oauth2 v0.18.0 + golang.org/x/text v0.14.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 4835505..58029da 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,27 @@ -github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= -github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= -github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo= @@ -29,8 +35,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= -github.com/jeremija/gosubmit v0.2.8 h1:mmSITBz9JxVtu8eqbN+zmmwX7Ij2RidQxhcwRVI4wqA= -github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= +github.com/jeremija/gosubmit v0.2.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc= +github.com/jeremija/gosubmit v0.2.7/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -41,68 +47,102 @@ github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/ github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= -github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zitadel/logging v0.6.2 h1:MW2kDDR0ieQynPZ0KIZPrh9ote2WkxfBif5QoARDQcU= -github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= -github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= -github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zitadel/logging v0.6.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank= +github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow= +github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0= +github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/testutil/gen/gen.go b/internal/testutil/gen/gen.go index 3e44b7d..370a0ed 100644 --- a/internal/testutil/gen/gen.go +++ b/internal/testutil/gen/gen.go @@ -8,8 +8,8 @@ import ( "fmt" "os" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" ) var custom = map[string]any{ diff --git a/internal/testutil/token.go b/internal/testutil/token.go index 72d08c5..9f059a2 100644 --- a/internal/testutil/token.go +++ b/internal/testutil/token.go @@ -8,9 +8,9 @@ import ( "errors" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/muhlemmer/gu" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // KeySet implements oidc.Keys diff --git a/pkg/client/client.go b/pkg/client/client.go index 2e1f536..9c93068 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -10,14 +10,13 @@ import ( "strings" "time" - "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/zitadel/logging" + "github.com/zitadel/oidc/v4/pkg/crypto" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" "go.opentelemetry.io/otel" "golang.org/x/oauth2" - - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" ) var ( @@ -42,7 +41,7 @@ func Discover(ctx context.Context, issuer string, httpClient *http.Client, wellK discoveryConfig := new(oidc.DiscoveryConfiguration) err = httphelper.HttpRequest(httpClient, req, &discoveryConfig) if err != nil { - return nil, errors.Join(oidc.ErrDiscoveryFailed, err) + return nil, err } if logger, ok := logging.FromContext(ctx); ok { logger.Debug("discover", "config", discoveryConfig) @@ -98,12 +97,7 @@ func CallEndSessionEndpoint(ctx context.Context, request any, authFn any, caller ctx, span := Tracer.Start(ctx, "CallEndSessionEndpoint") defer span.End() - endpoint := caller.GetEndSessionEndpoint() - if endpoint == "" { - return nil, fmt.Errorf("end session %w", ErrEndpointNotSet) - } - - req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn) + req, err := httphelper.FormRequest(ctx, caller.GetEndSessionEndpoint(), request, Encoder, authFn) if err != nil { return nil, err } @@ -149,12 +143,7 @@ func CallRevokeEndpoint(ctx context.Context, request any, authFn any, caller Rev ctx, span := Tracer.Start(ctx, "CallRevokeEndpoint") defer span.End() - endpoint := caller.GetRevokeEndpoint() - if endpoint == "" { - return fmt.Errorf("revoke %w", ErrEndpointNotSet) - } - - req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn) + req, err := httphelper.FormRequest(ctx, caller.GetRevokeEndpoint(), request, Encoder, authFn) if err != nil { return err } @@ -197,12 +186,12 @@ func CallTokenExchangeEndpoint(ctx context.Context, request any, authFn any, cal } func NewSignerFromPrivateKeyByte(key []byte, keyID string) (jose.Signer, error) { - privateKey, algorithm, err := crypto.BytesToPrivateKey(key) + privateKey, err := crypto.BytesToPrivateKey(key) if err != nil { return nil, err } signingKey := jose.SigningKey{ - Algorithm: algorithm, + Algorithm: jose.RS256, Key: &jose.JSONWebKey{Key: privateKey, KeyID: keyID}, } return jose.NewSigner(signingKey, &jose.SignerOptions{}) @@ -229,12 +218,7 @@ func CallDeviceAuthorizationEndpoint(ctx context.Context, request *oidc.ClientCr ctx, span := Tracer.Start(ctx, "CallDeviceAuthorizationEndpoint") defer span.End() - endpoint := caller.GetDeviceAuthorizationEndpoint() - if endpoint == "" { - return nil, fmt.Errorf("device authorization %w", ErrEndpointNotSet) - } - - req, err := httphelper.FormRequest(ctx, endpoint, request, Encoder, authFn) + req, err := httphelper.FormRequest(ctx, caller.GetDeviceAuthorizationEndpoint(), request, Encoder, authFn) if err != nil { return nil, err } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 9e21e8e..e06c825 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -5,7 +5,6 @@ import ( "net/http" "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,7 +22,7 @@ func TestDiscover(t *testing.T) { name string args args wantFields *wantFields - wantErr error + wantErr bool }{ { name: "spotify", // https://github.com/zitadel/oidc/issues/406 @@ -33,20 +32,17 @@ func TestDiscover(t *testing.T) { wantFields: &wantFields{ UILocalesSupported: true, }, - wantErr: nil, - }, - { - name: "discovery failed", - args: args{ - issuer: "https://example.com", - }, - wantErr: oidc.ErrDiscoveryFailed, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Discover(context.Background(), tt.args.issuer, http.DefaultClient, tt.args.wellKnownUrl...) - require.ErrorIs(t, err, tt.wantErr) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) if tt.wantFields == nil { return } diff --git a/pkg/client/errors.go b/pkg/client/errors.go deleted file mode 100644 index 47210e5..0000000 --- a/pkg/client/errors.go +++ /dev/null @@ -1,5 +0,0 @@ -package client - -import "errors" - -var ErrEndpointNotSet = errors.New("endpoint not set") diff --git a/pkg/client/integration_test.go b/pkg/client/integration_test.go index 86a9ab7..7827fd4 100644 --- a/pkg/client/integration_test.go +++ b/pkg/client/integration_test.go @@ -23,14 +23,14 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/oauth2" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/tokenexchange" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/example/server/exampleop" + "github.com/zitadel/oidc/v4/example/server/storage" + "github.com/zitadel/oidc/v4/pkg/client/rp" + "github.com/zitadel/oidc/v4/pkg/client/rs" + "github.com/zitadel/oidc/v4/pkg/client/tokenexchange" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) var Logger = slog.New( @@ -111,92 +111,6 @@ func testRelyingPartySession(t *testing.T, wrapServer bool) { } } -func TestRelyingPartyWithSigningAlgsFromDiscovery(t *testing.T) { - targetURL := "http://local-site" - localURL, err := url.Parse(targetURL + "/login?requestID=1234") - require.NoError(t, err, "local url") - - t.Log("------- start example OP ------") - seed := rand.New(rand.NewSource(int64(os.Getpid()) + time.Now().UnixNano())) - clientID := t.Name() + "-" + strconv.FormatInt(seed.Int63(), 25) - clientSecret := "secret" - client := storage.WebClient(clientID, clientSecret, targetURL) - storage.RegisterClients(client) - exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL)) - var dh deferredHandler - opServer := httptest.NewServer(&dh) - defer opServer.Close() - dh.Handler = exampleop.SetupServer(opServer.URL, exampleStorage, Logger, true) - - t.Log("------- create RP ------") - provider, err := rp.NewRelyingPartyOIDC( - CTX, - opServer.URL, - clientID, - clientSecret, - targetURL, - []string{"openid"}, - rp.WithSigningAlgsFromDiscovery(), - ) - require.NoError(t, err, "new rp") - - t.Log("------- run authorization code flow ------") - jar, err := cookiejar.New(nil) - require.NoError(t, err, "create cookie jar") - httpClient := &http.Client{ - Timeout: time.Second * 5, - CheckRedirect: func(_ *http.Request, _ []*http.Request) error { - return http.ErrUseLastResponse - }, - Jar: jar, - } - state := "state-" + strconv.FormatInt(seed.Int63(), 25) - capturedW := httptest.NewRecorder() - get := httptest.NewRequest("GET", localURL.String(), nil) - rp.AuthURLHandler(func() string { return state }, provider, - rp.WithPromptURLParam("Hello, World!", "Goodbye, World!"), - rp.WithURLParam("custom", "param"), - )(capturedW, get) - defer func() { - if t.Failed() { - t.Log("response body (redirect from RP to OP)", capturedW.Body.String()) - } - }() - resp := capturedW.Result() - startAuthURL, err := resp.Location() - require.NoError(t, err, "get redirect") - loginPageURL := getRedirect(t, "get redirect to login page", httpClient, startAuthURL) - form := getForm(t, "get login form", httpClient, loginPageURL) - defer func() { - if t.Failed() { - t.Logf("login form (unfilled): %s", string(form)) - } - }() - postLoginRedirectURL := fillForm(t, "fill login form", httpClient, form, loginPageURL, - gosubmit.Set("username", "test-user@local-site"), - gosubmit.Set("password", "verysecure"), - ) - codeBearingURL := getRedirect(t, "get redirect with code", httpClient, postLoginRedirectURL) - capturedW = httptest.NewRecorder() - get = httptest.NewRequest("GET", codeBearingURL.String(), nil) - var idToken string - redirect := func(w http.ResponseWriter, r *http.Request, newTokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty, info *oidc.UserInfo) { - idToken = newTokens.IDToken - http.Redirect(w, r, targetURL, http.StatusFound) - } - rp.CodeExchangeHandler(rp.UserinfoCallback(redirect), provider)(capturedW, get) - defer func() { - if t.Failed() { - t.Log("token exchange response body", capturedW.Body.String()) - require.GreaterOrEqual(t, capturedW.Code, 200, "captured response code") - } - }() - - t.Log("------- verify id token ------") - _, err = rp.VerifyIDToken[*oidc.IDTokenClaims](CTX, idToken, provider.IDTokenVerifier()) - require.NoError(t, err, "verify id token") -} - func TestResourceServerTokenExchange(t *testing.T) { for _, wrapServer := range []bool{false, true} { t.Run(fmt.Sprint("wrapServer ", wrapServer), func(t *testing.T) { diff --git a/pkg/client/jwt_profile.go b/pkg/client/jwt_profile.go index 98a54fd..356a549 100644 --- a/pkg/client/jwt_profile.go +++ b/pkg/client/jwt_profile.go @@ -6,8 +6,8 @@ import ( "golang.org/x/oauth2" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // JWTProfileExchange handles the oauth2 jwt profile exchange diff --git a/pkg/client/key.go b/pkg/client/key.go index 7f38311..0c01dd2 100644 --- a/pkg/client/key.go +++ b/pkg/client/key.go @@ -2,7 +2,7 @@ package client import ( "encoding/json" - "os" + "io/ioutil" ) const ( @@ -24,7 +24,7 @@ type KeyFile struct { } func ConfigFromKeyFile(path string) (*KeyFile, error) { - data, err := os.ReadFile(path) + data, err := ioutil.ReadFile(path) if err != nil { return nil, err } diff --git a/pkg/client/profile/jwt_profile.go b/pkg/client/profile/jwt_profile.go index fb351f0..93de660 100644 --- a/pkg/client/profile/jwt_profile.go +++ b/pkg/client/profile/jwt_profile.go @@ -5,11 +5,11 @@ import ( "net/http" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "golang.org/x/oauth2" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type TokenSource interface { diff --git a/pkg/client/rp/cli/cli.go b/pkg/client/rp/cli/cli.go index 10edaa7..5e20e63 100644 --- a/pkg/client/rp/cli/cli.go +++ b/pkg/client/rp/cli/cli.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client/rp" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) const ( diff --git a/pkg/client/rp/delegation.go b/pkg/client/rp/delegation.go index fb4fc63..e3ad170 100644 --- a/pkg/client/rp/delegation.go +++ b/pkg/client/rp/delegation.go @@ -1,7 +1,7 @@ package rp import ( - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange" + "github.com/zitadel/oidc/v4/pkg/oidc/grants/tokenexchange" ) // DelegationTokenRequest is an implementation of TokenExchangeRequest diff --git a/pkg/client/rp/device.go b/pkg/client/rp/device.go index 1fadd56..5c0b994 100644 --- a/pkg/client/rp/device.go +++ b/pkg/client/rp/device.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) { diff --git a/pkg/client/rp/jwks.go b/pkg/client/rp/jwks.go index 0ccbad2..7b94aff 100644 --- a/pkg/client/rp/jwks.go +++ b/pkg/client/rp/jwks.go @@ -7,11 +7,11 @@ import ( "net/http" "sync" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet { @@ -217,7 +217,7 @@ func (r *remoteKeySet) fetchRemoteKeys(ctx context.Context) ([]jose.JSONWebKey, ctx, span := client.Tracer.Start(ctx, "fetchRemoteKeys") defer span.End() - req, err := http.NewRequestWithContext(ctx, "GET", r.jwksURL, nil) + req, err := http.NewRequest("GET", r.jwksURL, nil) if err != nil { return nil, fmt.Errorf("oidc: can't create request: %v", err) } diff --git a/pkg/client/rp/relying_party.go b/pkg/client/rp/relying_party.go index c2759a2..cbfe83b 100644 --- a/pkg/client/rp/relying_party.go +++ b/pkg/client/rp/relying_party.go @@ -9,15 +9,15 @@ import ( "net/url" "time" - "github.com/go-jose/go-jose/v4" + "github.com/go-jose/go-jose/v3" "github.com/google/uuid" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/logging" + "github.com/zitadel/oidc/v4/pkg/client" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) const ( @@ -60,7 +60,7 @@ type RelyingParty interface { // UserinfoEndpoint returns the userinfo UserinfoEndpoint() string - // GetDeviceAuthorizationEndpoint returns the endpoint which can + // GetDeviceAuthorizationEndpoint returns the enpoint which can // be used to start a DeviceAuthorization flow. GetDeviceAuthorizationEndpoint() string @@ -90,13 +90,12 @@ var DefaultUnauthorizedHandler UnauthorizedHandler = func(w http.ResponseWriter, } type relyingParty struct { - issuer string - DiscoveryEndpoint string - endpoints Endpoints - oauthConfig *oauth2.Config - oauth2Only bool - pkce bool - useSigningAlgsFromDiscovery bool + issuer string + DiscoveryEndpoint string + endpoints Endpoints + oauthConfig *oauth2.Config + oauth2Only bool + pkce bool httpClient *http.Client cookieHandler *httphelper.CookieHandler @@ -239,9 +238,6 @@ func NewRelyingPartyOIDC(ctx context.Context, issuer, clientID, clientSecret, re if err != nil { return nil, err } - if rp.useSigningAlgsFromDiscovery { - rp.verifierOpts = append(rp.verifierOpts, WithSupportedSigningAlgorithms(discoveryConfiguration.IDTokenSigningAlgValuesSupported...)) - } endpoints := GetEndpoints(discoveryConfiguration) rp.oauthConfig.Endpoint = endpoints.Endpoint rp.endpoints = endpoints @@ -352,15 +348,6 @@ func WithLogger(logger *slog.Logger) Option { } } -// WithSigningAlgsFromDiscovery appends the [WithSupportedSigningAlgorithms] option to the Verifier Options. -// The algorithms returned in the `id_token_signing_alg_values_supported` from the discovery response will be set. -func WithSigningAlgsFromDiscovery() Option { - return func(rp *relyingParty) error { - rp.useSigningAlgsFromDiscovery = true - return nil - } -} - type SignerFromKey func() (jose.Signer, error) func SignerFromKeyPath(path string) SignerFromKey { @@ -401,7 +388,7 @@ func AuthURL(state string, rp RelyingParty, opts ...AuthURLOpt) string { // AuthURLHandler extends the `AuthURL` method with a http redirect handler // including handling setting cookie for secure `state` transfer. -// Custom parameters can optionally be set to the redirect URL. +// Custom paramaters can optionally be set to the redirect URL. func AuthURLHandler(stateFn func() string, rp RelyingParty, urlParam ...URLParamOpt) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { opts := make([]AuthURLOpt, len(urlParam)) @@ -541,7 +528,7 @@ func CodeExchangeHandler[C oidc.IDClaims](callback CodeExchangeCallback[C], rp R rp.CookieHandler().DeleteCookie(w, pkceCode) } if rp.Signer() != nil { - assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer(), rp.OAuthConfig().Endpoint.TokenURL}, time.Hour, rp.Signer()) + assertion, err := client.SignedJWTProfileAssertion(rp.OAuthConfig().ClientID, []string{rp.Issuer()}, time.Hour, rp.Signer()) if err != nil { unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp) return @@ -655,7 +642,7 @@ func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints { } } -// withURLParam sets custom url parameters. +// withURLParam sets custom url paramaters. // This is the generalized, unexported, function used by both // URLParamOpt and AuthURLOpt. func withURLParam(key, value string) func() []oauth2.AuthCodeOption { @@ -734,11 +721,11 @@ func (t tokenEndpointCaller) TokenEndpoint() string { type RefreshTokenRequest struct { RefreshToken string `schema:"refresh_token"` - Scopes oidc.SpaceDelimitedArray `schema:"scope,omitempty"` - ClientID string `schema:"client_id,omitempty"` - ClientSecret string `schema:"client_secret,omitempty"` - ClientAssertion string `schema:"client_assertion,omitempty"` - ClientAssertionType string `schema:"client_assertion_type,omitempty"` + Scopes oidc.SpaceDelimitedArray `schema:"scope"` + ClientID string `schema:"client_id"` + ClientSecret string `schema:"client_secret"` + ClientAssertion string `schema:"client_assertion"` + ClientAssertionType string `schema:"client_assertion_type"` GrantType oidc.GrantType `schema:"grant_type"` } @@ -747,7 +734,7 @@ type RefreshTokenRequest struct { // the old one should be considered invalid. // // In case the RP is not OAuth2 only and an IDToken was part of the response, -// the IDToken and AccessToken will be verified +// the IDToken and AccessToken will be verfied // and the IDToken and IDTokenClaims fields will be populated in the returned object. func RefreshTokens[C oidc.IDClaims](ctx context.Context, rp RelyingParty, refreshToken, clientAssertion, clientAssertionType string) (*oidc.Tokens[C], error) { ctx, span := client.Tracer.Start(ctx, "RefreshTokens") diff --git a/pkg/client/rp/relying_party_test.go b/pkg/client/rp/relying_party_test.go index b3bb6ee..67bad18 100644 --- a/pkg/client/rp/relying_party_test.go +++ b/pkg/client/rp/relying_party_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" "golang.org/x/oauth2" ) diff --git a/pkg/client/rp/tockenexchange.go b/pkg/client/rp/tockenexchange.go index aa2cf99..1b2edb8 100644 --- a/pkg/client/rp/tockenexchange.go +++ b/pkg/client/rp/tockenexchange.go @@ -5,7 +5,7 @@ import ( "golang.org/x/oauth2" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange" + "github.com/zitadel/oidc/v4/pkg/oidc/grants/tokenexchange" ) // TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange` diff --git a/pkg/client/rp/userinfo_example_test.go b/pkg/client/rp/userinfo_example_test.go index 78e014e..8571a7d 100644 --- a/pkg/client/rp/userinfo_example_test.go +++ b/pkg/client/rp/userinfo_example_test.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client/rp" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type UserInfo struct { diff --git a/pkg/client/rp/verifier.go b/pkg/client/rp/verifier.go index 0088b81..477417f 100644 --- a/pkg/client/rp/verifier.go +++ b/pkg/client/rp/verifier.go @@ -4,10 +4,10 @@ import ( "context" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // VerifyTokens implement the Token Response Validation as defined in OIDC specification @@ -73,10 +73,8 @@ func VerifyIDToken[C oidc.Claims](ctx context.Context, token string, v *IDTokenV return nilClaims, err } - if v.Nonce != nil { - if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil { - return nilClaims, err - } + if err = oidc.CheckNonce(claims, v.Nonce(ctx)); err != nil { + return nilClaims, err } if err = oidc.CheckAuthorizationContextClassReference(claims, v.ACR); err != nil { diff --git a/pkg/client/rp/verifier_test.go b/pkg/client/rp/verifier_test.go index 38f5a4a..7f7d722 100644 --- a/pkg/client/rp/verifier_test.go +++ b/pkg/client/rp/verifier_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func TestVerifyTokens(t *testing.T) { @@ -100,21 +100,22 @@ func TestVerifyIDToken(t *testing.T) { MaxAge: 2 * time.Minute, ACR: tu.ACRVerify, Nonce: func(context.Context) string { return tu.ValidNonce }, - ClientID: tu.ValidClientID, } tests := []struct { - name string - tokenClaims func() (string, *oidc.IDTokenClaims) - customVerifier func(verifier *IDTokenVerifier) - wantErr bool + name string + clientID string + tokenClaims func() (string, *oidc.IDTokenClaims) + wantErr bool }{ { name: "success", + clientID: tu.ValidClientID, tokenClaims: tu.ValidIDToken, }, { - name: "custom claims", + name: "custom claims", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDTokenCustom( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -124,31 +125,21 @@ func TestVerifyIDToken(t *testing.T) { ) }, }, - { - name: "skip nonce check", - customVerifier: func(verifier *IDTokenVerifier) { - verifier.Nonce = nil - }, - tokenClaims: func() (string, *oidc.IDTokenClaims) { - return tu.NewIDToken( - tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, - tu.ValidExpiration, tu.ValidAuthTime, "foo", - tu.ValidACR, tu.ValidAMR, tu.ValidClientID, tu.ValidSkew, "", - ) - }, - }, { name: "parse err", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return "~~~~", nil }, wantErr: true, }, { name: "invalid signature", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.InvalidSignatureToken, nil }, wantErr: true, }, { - name: "empty subject", + name: "empty subject", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, "", tu.ValidAudience, @@ -159,7 +150,8 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "wrong issuer", + name: "wrong issuer", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( "foo", tu.ValidSubject, tu.ValidAudience, @@ -170,15 +162,14 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "wrong clientID", - customVerifier: func(verifier *IDTokenVerifier) { - verifier.ClientID = "foo" - }, + name: "wrong clientID", + clientID: "foo", tokenClaims: tu.ValidIDToken, wantErr: true, }, { - name: "expired", + name: "expired", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -189,7 +180,8 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "wrong IAT", + name: "wrong IAT", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -200,7 +192,8 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "wrong acr", + name: "wrong acr", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -211,7 +204,8 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "expired auth", + name: "expired auth", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -222,7 +216,8 @@ func TestVerifyIDToken(t *testing.T) { wantErr: true, }, { - name: "wrong nonce", + name: "wrong nonce", + clientID: tu.ValidClientID, tokenClaims: func() (string, *oidc.IDTokenClaims) { return tu.NewIDToken( tu.ValidIssuer, tu.ValidSubject, tu.ValidAudience, @@ -236,10 +231,7 @@ func TestVerifyIDToken(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { token, want := tt.tokenClaims() - if tt.customVerifier != nil { - tt.customVerifier(verifier) - } - + verifier.ClientID = tt.clientID got, err := VerifyIDToken[*oidc.IDTokenClaims](context.Background(), token, verifier) if tt.wantErr { assert.Error(t, err) diff --git a/pkg/client/rp/verifier_tokens_example_test.go b/pkg/client/rp/verifier_tokens_example_test.go index 7ae68d6..b9e11e6 100644 --- a/pkg/client/rp/verifier_tokens_example_test.go +++ b/pkg/client/rp/verifier_tokens_example_test.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/client/rp" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // MyCustomClaims extends the TokenClaims base, diff --git a/pkg/client/rs/introspect_example_test.go b/pkg/client/rs/introspect_example_test.go index 1f67d11..cbfd11b 100644 --- a/pkg/client/rs/introspect_example_test.go +++ b/pkg/client/rs/introspect_example_test.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client/rs" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type IntrospectionResponse struct { diff --git a/pkg/client/rs/resource_server.go b/pkg/client/rs/resource_server.go index 993796e..e420b52 100644 --- a/pkg/client/rs/resource_server.go +++ b/pkg/client/rs/resource_server.go @@ -6,9 +6,9 @@ import ( "net/http" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/client" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type ResourceServer interface { diff --git a/pkg/client/rs/resource_server_test.go b/pkg/client/rs/resource_server_test.go index afd7441..83e6d4b 100644 --- a/pkg/client/rs/resource_server_test.go +++ b/pkg/client/rs/resource_server_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func TestNewResourceServer(t *testing.T) { diff --git a/pkg/client/tokenexchange/tokenexchange.go b/pkg/client/tokenexchange/tokenexchange.go index 9cc1328..dfeb88f 100644 --- a/pkg/client/tokenexchange/tokenexchange.go +++ b/pkg/client/tokenexchange/tokenexchange.go @@ -6,10 +6,10 @@ import ( "net/http" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "github.com/go-jose/go-jose/v4" + "github.com/go-jose/go-jose/v3" + "github.com/zitadel/oidc/v4/pkg/client" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type TokenExchanger interface { diff --git a/pkg/crypto/hash.go b/pkg/crypto/hash.go index 14acdee..0ed2774 100644 --- a/pkg/crypto/hash.go +++ b/pkg/crypto/hash.go @@ -8,7 +8,7 @@ import ( "fmt" "hash" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) var ErrUnsupportedAlgorithm = errors.New("unsupported signing algorithm") @@ -21,14 +21,6 @@ func GetHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) { return sha512.New384(), nil case jose.RS512, jose.ES512, jose.PS512: return sha512.New(), nil - - // There is no published spec for this yet, but we have confirmation it will get published. - // There is consensus here: https://bitbucket.org/openid/connect/issues/1125/_hash-algorithm-for-eddsa-id-tokens - // Currently Go and go-jose only supports the ed25519 curve key for EdDSA, so we can safely assume sha512 here. - // It is unlikely ed448 will ever be supported: https://github.com/golang/go/issues/29390 - case jose.EdDSA: - return sha512.New(), nil - default: return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, sigAlgorithm) } diff --git a/pkg/crypto/key.go b/pkg/crypto/key.go index 12bca28..79e2046 100644 --- a/pkg/crypto/key.go +++ b/pkg/crypto/key.go @@ -1,45 +1,22 @@ package crypto import ( - "crypto" - "crypto/ecdsa" - "crypto/ed25519" "crypto/rsa" "crypto/x509" "encoding/pem" "errors" - - "github.com/go-jose/go-jose/v4" ) -var ( - ErrPEMDecode = errors.New("PEM decode failed") - ErrUnsupportedFormat = errors.New("key is neither in PKCS#1 nor PKCS#8 format") - ErrUnsupportedPrivateKey = errors.New("unsupported key type, must be RSA, ECDSA or ED25519 private key") -) - -func BytesToPrivateKey(b []byte) (crypto.PublicKey, jose.SignatureAlgorithm, error) { +func BytesToPrivateKey(b []byte) (*rsa.PrivateKey, error) { block, _ := pem.Decode(b) if block == nil { - return nil, "", ErrPEMDecode + return nil, errors.New("PEM decode failed") } - privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err == nil { - return privateKey, jose.RS256, nil - } - key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { - return nil, "", ErrUnsupportedFormat - } - switch privateKey := key.(type) { - case *rsa.PrivateKey: - return privateKey, jose.RS256, nil - case ed25519.PrivateKey: - return privateKey, jose.EdDSA, nil - case *ecdsa.PrivateKey: - return privateKey, jose.ES256, nil - default: - return nil, "", ErrUnsupportedPrivateKey + return nil, err } + + return key, nil } diff --git a/pkg/crypto/key_test.go b/pkg/crypto/key_test.go index a6fa493..608b627 100644 --- a/pkg/crypto/key_test.go +++ b/pkg/crypto/key_test.go @@ -1,64 +1,21 @@ package crypto_test import ( - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "testing" - "github.com/go-jose/go-jose/v4" "github.com/stretchr/testify/assert" - zcrypto "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" + "github.com/zitadel/oidc/v4/pkg/crypto" ) -func TestBytesToPrivateKey(t *testing.T) { - type args struct { - key []byte - } - type want struct { - key crypto.Signer - algorithm jose.SignatureAlgorithm - err error - } - tests := []struct { - name string - args args - want want - }{ - { - name: "PEMDecodeError", - args: args{ - key: []byte("The non-PEM sequence"), - }, - want: want{ - err: zcrypto.ErrPEMDecode, - }, - }, - { - name: "PKCS#1 RSA", - args: args{ - key: []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu -KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm -o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k -TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7 -9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy -v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs -/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00 ------END RSA PRIVATE KEY-----`), - }, - want: want{ - key: &rsa.PrivateKey{}, - algorithm: jose.RS256, - err: nil, - }, - }, - { - name: "PKCS#8 RSA", - args: args{ - key: []byte(`-----BEGIN PRIVATE KEY----- +func TestBytesToPrivateKey(tt *testing.T) { + tt.Run("PEMDecodeError", func(t *testing.T) { + _, err := crypto.BytesToPrivateKey([]byte("The non-PEM sequence")) + assert.EqualError(t, err, "PEM decode failed") + }) + + tt.Run("InvalidKeyFormat", func(t *testing.T) { + _, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfaDB7pK/fmP/I 7IusSK8lTCBnPZghqIbVLt2QHYAMoEF1CaF4F4rxo2vl1Mt8gwsq4T3osQFZMvnL YHb7KNyUoJgTjLxJQADv2u4Q3U38heAzK5Tp4ry4MCnuyJIqAPK1GiruwEq4zQrx @@ -85,50 +42,21 @@ srJnjF0H8oKmAY6hw+1Tm/n/b08p+RyL48TgVSE2vhUCgYA3BWpkD4PlCcn/FZsq OrLFyFXI6jIaxskFtsRW1IxxIlAdZmxfB26P/2gx6VjLdxJI/RRPkJyEN2dP7CbR BDjb565dy1O9D6+UrY70Iuwjz+OcALRBBGTaiF2pLn6IhSzNI2sy/tXX8q8dBlg9 OFCrqT/emes3KytTPfa5NZtYeQ== ------END PRIVATE KEY-----`), - }, - want: want{ - key: &rsa.PrivateKey{}, - algorithm: jose.RS256, - err: nil, - }, - }, - { - name: "PKCS#8 ECDSA", - args: args{ - key: []byte(`-----BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgwwOZSU4GlP7ps/Wp -V6o0qRwxultdfYo/uUuj48QZjSuhRANCAATMiI2Han+ABKmrk5CNlxRAGC61w4d3 -G4TAeuBpyzqJ7x/6NjCxoQzJzZHtNjIfjVATI59XFZWF59GhtSZbShAr ------END PRIVATE KEY-----`), - }, - want: want{ - key: &ecdsa.PrivateKey{}, - algorithm: jose.ES256, - err: nil, - }, - }, - { - name: "PKCS#8 ED25519", - args: args{ - key: []byte(`-----BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIHu6ZtDsjjauMasBxnS9Fg87UJwKfcT/oiq6S0ktbky8 ------END PRIVATE KEY-----`), - }, - want: want{ - key: ed25519.PrivateKey{}, - algorithm: jose.EdDSA, - err: nil, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - key, algorithm, err := zcrypto.BytesToPrivateKey(tt.args.key) - assert.IsType(t, tt.want.key, key) - assert.Equal(t, tt.want.algorithm, algorithm) - assert.ErrorIs(t, tt.want.err, err) - }) +-----END PRIVATE KEY-----`)) + assert.EqualError(t, err, "x509: failed to parse private key (use ParsePKCS8PrivateKey instead for this key format)") + }) - } + tt.Run("Ok", func(t *testing.T) { + key, err := crypto.BytesToPrivateKey([]byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu +KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm +o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k +TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7 +9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy +v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs +/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00 +-----END RSA PRIVATE KEY-----`)) + assert.NoError(t, err) + assert.NotNil(t, key) + }) } diff --git a/pkg/crypto/sign.go b/pkg/crypto/sign.go index 937a846..a197955 100644 --- a/pkg/crypto/sign.go +++ b/pkg/crypto/sign.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) func Sign(object any, signer jose.Signer) (string, error) { diff --git a/pkg/http/http.go b/pkg/http/http.go index aa0ff6f..495faaf 100644 --- a/pkg/http/http.go +++ b/pkg/http/http.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/oidc" ) var DefaultHTTPClient = &http.Client{ diff --git a/pkg/oidc/code_challenge.go b/pkg/oidc/code_challenge.go index 0c593df..b3558e2 100644 --- a/pkg/oidc/code_challenge.go +++ b/pkg/oidc/code_challenge.go @@ -3,7 +3,7 @@ package oidc import ( "crypto/sha256" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" + "github.com/zitadel/oidc/v4/pkg/crypto" ) const ( diff --git a/pkg/oidc/device_authorization.go b/pkg/oidc/device_authorization.go index a6417ba..68b8efa 100644 --- a/pkg/oidc/device_authorization.go +++ b/pkg/oidc/device_authorization.go @@ -1,7 +1,5 @@ package oidc -import "encoding/json" - // DeviceAuthorizationRequest implements // https://www.rfc-editor.org/rfc/rfc8628#section-3.1, // 3.1 Device Authorization Request. @@ -22,26 +20,6 @@ type DeviceAuthorizationResponse struct { Interval int `json:"interval,omitempty"` } -func (resp *DeviceAuthorizationResponse) UnmarshalJSON(data []byte) error { - type Alias DeviceAuthorizationResponse - aux := &struct { - // workaround misspelling of verification_uri - // https://stackoverflow.com/q/76696956/5690223 - // https://developers.google.com/identity/protocols/oauth2/limited-input-device?hl=fr#success-response - VerificationURL string `json:"verification_url"` - *Alias - }{ - Alias: (*Alias)(resp), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - if resp.VerificationURI == "" { - resp.VerificationURI = aux.VerificationURL - } - return nil -} - // DeviceAccessTokenRequest implements // https://www.rfc-editor.org/rfc/rfc8628#section-3.4, // Device Access Token Request. diff --git a/pkg/oidc/device_authorization_test.go b/pkg/oidc/device_authorization_test.go deleted file mode 100644 index c4c6637..0000000 --- a/pkg/oidc/device_authorization_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package oidc - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestDeviceAuthorizationResponse_UnmarshalJSON(t *testing.T) { - jsonStr := `{ - "device_code": "deviceCode", - "user_code": "userCode", - "verification_url": "http://example.com/verify", - "expires_in": 3600, - "interval": 5 - }` - - expected := &DeviceAuthorizationResponse{ - DeviceCode: "deviceCode", - UserCode: "userCode", - VerificationURI: "http://example.com/verify", - ExpiresIn: 3600, - Interval: 5, - } - - var resp DeviceAuthorizationResponse - err := resp.UnmarshalJSON([]byte(jsonStr)) - assert.NoError(t, err) - assert.Equal(t, expected, &resp) -} diff --git a/pkg/oidc/discovery.go b/pkg/oidc/discovery.go index 62288d1..14fce5e 100644 --- a/pkg/oidc/discovery.go +++ b/pkg/oidc/discovery.go @@ -145,14 +145,6 @@ type DiscoveryConfiguration struct { // OPTermsOfServiceURI is a URL the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service. OPTermsOfServiceURI string `json:"op_tos_uri,omitempty"` - - // BackChannelLogoutSupported specifies whether the OP supports back-channel logout (https://openid.net/specs/openid-connect-backchannel-1_0.html), - // with true indicating support. If omitted, the default value is false. - BackChannelLogoutSupported bool `json:"backchannel_logout_supported,omitempty"` - - // BackChannelLogoutSessionSupported specifies whether the OP can pass a sid (session ID) Claim in the Logout Token to identify the RP session with the OP. - // If supported, the sid Claim is also included in ID Tokens issued by the OP. If omitted, the default value is false. - BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported,omitempty"` } type AuthMethod string diff --git a/pkg/oidc/error.go b/pkg/oidc/error.go index d93cf44..2f0572d 100644 --- a/pkg/oidc/error.go +++ b/pkg/oidc/error.go @@ -1,7 +1,6 @@ package oidc import ( - "encoding/json" "errors" "fmt" "log/slog" @@ -133,28 +132,7 @@ type Error struct { ErrorType errorType `json:"error" schema:"error"` Description string `json:"error_description,omitempty" schema:"error_description,omitempty"` State string `json:"state,omitempty" schema:"state,omitempty"` - SessionState string `json:"session_state,omitempty" schema:"session_state,omitempty"` redirectDisabled bool `schema:"-"` - returnParent bool `schema:"-"` -} - -func (e *Error) MarshalJSON() ([]byte, error) { - m := struct { - Error errorType `json:"error"` - ErrorDescription string `json:"error_description,omitempty"` - State string `json:"state,omitempty"` - SessionState string `json:"session_state,omitempty"` - Parent string `json:"parent,omitempty"` - }{ - Error: e.ErrorType, - ErrorDescription: e.Description, - State: e.State, - SessionState: e.SessionState, - } - if e.returnParent { - m.Parent = e.Parent.Error() - } - return json.Marshal(m) } func (e *Error) Error() string { @@ -179,8 +157,7 @@ func (e *Error) Is(target error) bool { } return e.ErrorType == t.ErrorType && (e.Description == t.Description || t.Description == "") && - (e.State == t.State || t.State == "") && - (e.SessionState == t.SessionState || t.SessionState == "") + (e.State == t.State || t.State == "") } func (e *Error) WithParent(err error) *Error { @@ -188,18 +165,6 @@ func (e *Error) WithParent(err error) *Error { return e } -// WithReturnParentToClient allows returning the set parent error to the HTTP client. -// Currently it only supports setting the parent inside JSON responses, not redirect URLs. -// As Go errors don't unmarshal well, only the marshaller is implemented for the moment. -// -// Warning: parent errors may contain sensitive data or unwanted details about the server status. -// Also, the `parent` field is not a standard error field and might confuse certain clients -// that require fully compliant responses. -func (e *Error) WithReturnParentToClient(b bool) *Error { - e.returnParent = b - return e -} - func (e *Error) WithDescription(desc string, args ...any) *Error { e.Description = fmt.Sprintf(desc, args...) return e @@ -246,9 +211,6 @@ func (e *Error) LogValue() slog.Value { if e.State != "" { attrs = append(attrs, slog.String("state", e.State)) } - if e.SessionState != "" { - attrs = append(attrs, slog.String("session_state", e.SessionState)) - } if e.redirectDisabled { attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled)) } diff --git a/pkg/oidc/error_test.go b/pkg/oidc/error_test.go index 40d30b1..2eeb4e6 100644 --- a/pkg/oidc/error_test.go +++ b/pkg/oidc/error_test.go @@ -1,14 +1,11 @@ package oidc import ( - "encoding/json" - "errors" "io" "log/slog" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDefaultToServerError(t *testing.T) { @@ -154,39 +151,3 @@ func TestError_LogValue(t *testing.T) { }) } } - -func TestError_MarshalJSON(t *testing.T) { - tests := []struct { - name string - e *Error - want string - }{ - { - name: "simple error", - e: ErrAccessDenied(), - want: `{"error":"access_denied","error_description":"The authorization request was denied."}`, - }, - { - name: "with description", - e: ErrAccessDenied().WithDescription("oops"), - want: `{"error":"access_denied","error_description":"oops"}`, - }, - { - name: "with parent", - e: ErrServerError().WithParent(errors.New("oops")), - want: `{"error":"server_error"}`, - }, - { - name: "with return parent", - e: ErrServerError().WithParent(errors.New("oops")).WithReturnParentToClient(true), - want: `{"error":"server_error","parent":"oops"}`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.e) - require.NoError(t, err) - assert.JSONEq(t, tt.want, string(got)) - }) - } -} diff --git a/pkg/oidc/keyset.go b/pkg/oidc/keyset.go index a8b89b0..6031c01 100644 --- a/pkg/oidc/keyset.go +++ b/pkg/oidc/keyset.go @@ -6,9 +6,8 @@ import ( "crypto/ed25519" "crypto/rsa" "errors" - "strings" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) const ( @@ -93,17 +92,17 @@ func FindMatchingKey(keyID, use, expectedAlg string, keys ...jose.JSONWebKey) (k } func algToKeyType(key any, alg string) bool { - if strings.HasPrefix(alg, "RS") || strings.HasPrefix(alg, "PS") { + switch alg[0] { + case 'R', 'P': _, ok := key.(*rsa.PublicKey) return ok - } - if strings.HasPrefix(alg, "ES") { + case 'E': _, ok := key.(*ecdsa.PublicKey) return ok - } - if alg == string(jose.EdDSA) { - _, ok := key.(ed25519.PublicKey) + case 'O': + _, ok := key.(*ed25519.PublicKey) return ok + default: + return false } - return false } diff --git a/pkg/oidc/keyset_test.go b/pkg/oidc/keyset_test.go index e01074e..f8641f2 100644 --- a/pkg/oidc/keyset_test.go +++ b/pkg/oidc/keyset_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) func TestFindKey(t *testing.T) { diff --git a/pkg/oidc/session.go b/pkg/oidc/session.go index 39f9f08..b470d1e 100644 --- a/pkg/oidc/session.go +++ b/pkg/oidc/session.go @@ -1,12 +1,10 @@ package oidc // EndSessionRequest for the RP-Initiated Logout according to: -// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout +//https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout type EndSessionRequest struct { - IdTokenHint string `schema:"id_token_hint"` - LogoutHint string `schema:"logout_hint"` - ClientID string `schema:"client_id"` - PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"` - State string `schema:"state"` - UILocales Locales `schema:"ui_locales"` + IdTokenHint string `schema:"id_token_hint"` + ClientID string `schema:"client_id"` + PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"` + State string `schema:"state"` } diff --git a/pkg/oidc/token.go b/pkg/oidc/token.go index 4b43dcb..19b37dd 100644 --- a/pkg/oidc/token.go +++ b/pkg/oidc/token.go @@ -5,12 +5,11 @@ import ( "os" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "golang.org/x/oauth2" "github.com/muhlemmer/gu" - - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" + "github.com/zitadel/oidc/v4/pkg/crypto" ) const ( @@ -117,7 +116,6 @@ func NewAccessTokenClaims(issuer, subject string, audience []string, expiration Expiration: FromTime(expiration), IssuedAt: FromTime(now), NotBefore: FromTime(now), - ClientID: clientID, JWTID: jwtid, }, } @@ -230,13 +228,12 @@ func (c *ActorClaims) UnmarshalJSON(data []byte) error { } type AccessTokenResponse struct { - AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"` - TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"` - RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"` - ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"` - IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"` - State string `json:"state,omitempty" schema:"state,omitempty"` - Scope SpaceDelimitedArray `json:"scope,omitempty" schema:"scope,omitempty"` + AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"` + TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"` + RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"` + ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"` + IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"` + State string `json:"state,omitempty" schema:"state,omitempty"` } type JWTProfileAssertionClaims struct { @@ -347,12 +344,12 @@ func AppendClientIDToAudience(clientID string, audience []string) []string { } func GenerateJWTProfileToken(assertion *JWTProfileAssertionClaims) (string, error) { - privateKey, algorithm, err := crypto.BytesToPrivateKey(assertion.PrivateKey) + privateKey, err := crypto.BytesToPrivateKey(assertion.PrivateKey) if err != nil { return "", err } key := jose.SigningKey{ - Algorithm: algorithm, + Algorithm: jose.RS256, Key: &jose.JSONWebKey{Key: privateKey, KeyID: assertion.PrivateKeyID}, } signer, err := jose.NewSigner(key, &jose.SignerOptions{}) @@ -383,40 +380,3 @@ type TokenExchangeResponse struct { // if the requested_token_type was Access Token and scope contained openid. IDToken string `json:"id_token,omitempty"` } - -type LogoutTokenClaims struct { - Issuer string `json:"iss,omitempty"` - Subject string `json:"sub,omitempty"` - Audience Audience `json:"aud,omitempty"` - IssuedAt Time `json:"iat,omitempty"` - Expiration Time `json:"exp,omitempty"` - JWTID string `json:"jti,omitempty"` - Events map[string]any `json:"events,omitempty"` - SessionID string `json:"sid,omitempty"` - Claims map[string]any `json:"-"` -} - -type ltcAlias LogoutTokenClaims - -func (i *LogoutTokenClaims) MarshalJSON() ([]byte, error) { - return mergeAndMarshalClaims((*ltcAlias)(i), i.Claims) -} - -func (i *LogoutTokenClaims) UnmarshalJSON(data []byte) error { - return unmarshalJSONMulti(data, (*ltcAlias)(i), &i.Claims) -} - -func NewLogoutTokenClaims(issuer, subject string, audience Audience, expiration time.Time, jwtID, sessionID string, skew time.Duration) *LogoutTokenClaims { - return &LogoutTokenClaims{ - Issuer: issuer, - Subject: subject, - Audience: audience, - IssuedAt: FromTime(time.Now().Add(-skew)), - Expiration: FromTime(expiration), - JWTID: jwtID, - Events: map[string]any{ - "http://schemas.openid.net/event/backchannel-logout": struct{}{}, - }, - SessionID: sessionID, - } -} diff --git a/pkg/oidc/token_request.go b/pkg/oidc/token_request.go index dadb205..b07b333 100644 --- a/pkg/oidc/token_request.go +++ b/pkg/oidc/token_request.go @@ -6,7 +6,7 @@ import ( "slices" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) const ( @@ -72,10 +72,10 @@ type AccessTokenRequest struct { Code string `schema:"code"` RedirectURI string `schema:"redirect_uri"` ClientID string `schema:"client_id"` - ClientSecret string `schema:"client_secret,omitempty"` - CodeVerifier string `schema:"code_verifier,omitempty"` - ClientAssertion string `schema:"client_assertion,omitempty"` - ClientAssertionType string `schema:"client_assertion_type,omitempty"` + ClientSecret string `schema:"client_secret"` + CodeVerifier string `schema:"code_verifier"` + ClientAssertion string `schema:"client_assertion"` + ClientAssertionType string `schema:"client_assertion_type"` } func (a *AccessTokenRequest) GrantType() GrantType { diff --git a/pkg/oidc/token_test.go b/pkg/oidc/token_test.go index 621cdbc..9f9ee2d 100644 --- a/pkg/oidc/token_test.go +++ b/pkg/oidc/token_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/stretchr/testify/assert" "golang.org/x/text/language" ) @@ -145,7 +145,6 @@ func TestNewAccessTokenClaims(t *testing.T) { Subject: "hello@me.com", Audience: Audience{"foo"}, Expiration: 12345, - ClientID: "foo", JWTID: "900", }, } @@ -242,39 +241,3 @@ func TestIDTokenClaims_GetUserInfo(t *testing.T) { got := idTokenData.GetUserInfo() assert.Equal(t, want, got) } - -func TestNewLogoutTokenClaims(t *testing.T) { - want := &LogoutTokenClaims{ - Issuer: "zitadel", - Subject: "hello@me.com", - Audience: Audience{"foo", "just@me.com"}, - Expiration: 12345, - JWTID: "jwtID", - Events: map[string]any{ - "http://schemas.openid.net/event/backchannel-logout": struct{}{}, - }, - SessionID: "sessionID", - Claims: nil, - } - - got := NewLogoutTokenClaims( - want.Issuer, - want.Subject, - want.Audience, - want.Expiration.AsTime(), - want.JWTID, - want.SessionID, - 1*time.Second, - ) - - // test if the dynamic timestamp is around now, - // allowing for a delta of 1, just in case we flip on - // either side of a second boundry. - nowMinusSkew := NowTime() - 1 - assert.InDelta(t, int64(nowMinusSkew), int64(got.IssuedAt), 1) - - // Make equal not fail on dynamic timestamp - got.IssuedAt = 0 - - assert.Equal(t, want, got) -} diff --git a/pkg/oidc/types.go b/pkg/oidc/types.go index 5d063b1..0e7152c 100644 --- a/pkg/oidc/types.go +++ b/pkg/oidc/types.go @@ -9,7 +9,7 @@ import ( "strings" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/muhlemmer/gu" "github.com/zitadel/schema" "golang.org/x/text/language" @@ -35,17 +35,6 @@ func (a *Audience) UnmarshalJSON(text []byte) error { return nil } -func (a *Audience) MarshalJSON() ([]byte, error) { - len := len(*a) - if len > 1 { - return json.Marshal(*a) - } else if len == 1 { - return json.Marshal((*a)[0]) - } - - return nil, errors.New("aud is empty") -} - type Display string func (d *Display) UnmarshalText(text []byte) error { @@ -93,9 +82,6 @@ func (l *Locale) MarshalJSON() ([]byte, error) { // to an empty value (language "und") and no error will be returned. // This state can be checked with the `l.Tag().IsRoot()` method. func (l *Locale) UnmarshalJSON(data []byte) error { - if len(data) == 0 || string(data) == "\"\"" { - return nil - } err := json.Unmarshal(data, &l.tag) if err == nil { return nil @@ -126,14 +112,6 @@ func ParseLocales(locales []string) Locales { return out } -func (l Locales) String() string { - tags := make([]string, len(l)) - for i, tag := range l { - tags[i] = tag.String() - } - return strings.Join(tags, " ") -} - // UnmarshalText implements the [encoding.TextUnmarshaler] interface. // It decodes an unquoted space seperated string into Locales. // Undefined language tags in the input are ignored and ommited from @@ -250,9 +228,6 @@ func NewEncoder() *schema.Encoder { e.RegisterEncoder(SpaceDelimitedArray{}, func(value reflect.Value) string { return value.Interface().(SpaceDelimitedArray).String() }) - e.RegisterEncoder(Locales{}, func(value reflect.Value) string { - return value.Interface().(Locales).String() - }) return e } diff --git a/pkg/oidc/types_test.go b/pkg/oidc/types_test.go index 53a9779..df93a73 100644 --- a/pkg/oidc/types_test.go +++ b/pkg/oidc/types_test.go @@ -217,30 +217,6 @@ func TestLocale_UnmarshalJSON(t *testing.T) { want dst wantErr bool }{ - { - name: "value not present", - input: `{}`, - wantErr: false, - want: dst{ - Locale: nil, - }, - }, - { - name: "null", - input: `{"locale": null}`, - wantErr: false, - want: dst{ - Locale: nil, - }, - }, - { - name: "empty, ignored", - input: `{"locale": ""}`, - wantErr: false, - want: dst{ - Locale: &Locale{}, - }, - }, { name: "afrikaans, ok", input: `{"locale": "af"}`, @@ -261,17 +237,16 @@ func TestLocale_UnmarshalJSON(t *testing.T) { wantErr: true, }, } + for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var got dst - err := json.Unmarshal([]byte(tt.input), &got) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.want, got) - }) + var got dst + err := json.Unmarshal([]byte(tt.input), &got) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) } } diff --git a/pkg/oidc/verifier.go b/pkg/oidc/verifier.go index d5e0213..c9cc716 100644 --- a/pkg/oidc/verifier.go +++ b/pkg/oidc/verifier.go @@ -7,11 +7,12 @@ import ( "encoding/json" "errors" "fmt" - "slices" "strings" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" + + str "github.com/zitadel/oidc/v4/pkg/strings" ) type Claims interface { @@ -40,7 +41,6 @@ type IDClaims interface { var ( ErrParse = errors.New("parsing of request failed") ErrIssuerInvalid = errors.New("issuer does not match") - ErrDiscoveryFailed = errors.New("OpenID Provider Configuration Discovery has failed") ErrSubjectMissing = errors.New("subject missing") ErrAudience = errors.New("audience is not valid") ErrAzpMissing = errors.New("authorized party is not set. If Token is valid for multiple audiences, azp must not be empty") @@ -83,7 +83,7 @@ type ACRVerifier func(string) error // if none of the provided values matches the acr claim func DefaultACRVerifier(possibleValues []string) ACRVerifier { return func(acr string) error { - if !slices.Contains(possibleValues, acr) { + if !str.Contains(possibleValues, acr) { return fmt.Errorf("expected one of: %v, got: %q", possibleValues, acr) } return nil @@ -122,7 +122,7 @@ func CheckIssuer(claims Claims, issuer string) error { } func CheckAudience(claims Claims, clientID string) error { - if !slices.Contains(claims.GetAudience(), clientID) { + if !str.Contains(claims.GetAudience(), clientID) { return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID) } @@ -148,13 +148,8 @@ func CheckAuthorizedParty(claims Claims, clientID string) error { } func CheckSignature(ctx context.Context, token string, payload []byte, claims ClaimsSignature, supportedSigAlgs []string, set KeySet) error { - jws, err := jose.ParseSigned(token, toJoseSignatureAlgorithms(supportedSigAlgs)) + jws, err := jose.ParseSigned(token) if err != nil { - if strings.HasPrefix(err.Error(), "go-jose/go-jose: unexpected signature algorithm") { - // TODO(v4): we should wrap errors instead of returning static ones. - // This is a workaround so we keep returning the same error for now. - return ErrSignatureUnsupportedAlg - } return ErrParse } if len(jws.Signatures) == 0 { @@ -164,6 +159,12 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl return ErrSignatureMultiple } sig := jws.Signatures[0] + if len(supportedSigAlgs) == 0 { + supportedSigAlgs = []string{"RS256"} + } + if !str.Contains(supportedSigAlgs, sig.Header.Algorithm) { + return fmt.Errorf("%w: id token signed with unsupported algorithm, expected %q got %q", ErrSignatureUnsupportedAlg, supportedSigAlgs, sig.Header.Algorithm) + } signedPayload, err := set.VerifySignature(ctx, jws) if err != nil { @@ -179,18 +180,6 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl return nil } -// TODO(v4): Use the new jose.SignatureAlgorithm type directly, instead of string. -func toJoseSignatureAlgorithms(algorithms []string) []jose.SignatureAlgorithm { - out := make([]jose.SignatureAlgorithm, len(algorithms)) - for i := range algorithms { - out[i] = jose.SignatureAlgorithm(algorithms[i]) - } - if len(out) == 0 { - out = append(out, jose.RS256, jose.ES256, jose.PS256) - } - return out -} - func CheckExpiration(claims Claims, offset time.Duration) error { expiration := claims.GetExpiration() if !time.Now().Add(offset).Before(expiration) { diff --git a/pkg/oidc/verifier_parse_test.go b/pkg/oidc/verifier_parse_test.go index 9cf5c1e..71a7e33 100644 --- a/pkg/oidc/verifier_parse_test.go +++ b/pkg/oidc/verifier_parse_test.go @@ -5,10 +5,10 @@ import ( "encoding/json" "testing" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func TestParseToken(t *testing.T) { diff --git a/pkg/op/auth_request.go b/pkg/op/auth_request.go index b1434cc..8891cf5 100644 --- a/pkg/op/auth_request.go +++ b/pkg/op/auth_request.go @@ -11,13 +11,13 @@ import ( "net" "net/http" "net/url" - "slices" "strings" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/bmatcuk/doublestar/v4" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + str "github.com/zitadel/oidc/v4/pkg/strings" ) type AuthRequest interface { @@ -38,13 +38,6 @@ type AuthRequest interface { Done() bool } -// AuthRequestSessionState should be implemented if [OpenID Connect Session Management](https://openid.net/specs/openid-connect-session-1_0.html) is supported -type AuthRequestSessionState interface { - // GetSessionState returns session_state. - // session_state is related to OpenID Connect Session Management. - GetSessionState() string -} - type Authorizer interface { Storage() Storage Decoder() httphelper.Decoder @@ -62,19 +55,13 @@ type AuthorizeValidator interface { ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error) } -type CodeResponseType struct { - Code string `schema:"code"` - State string `schema:"state,omitempty"` - SessionState string `schema:"session_state,omitempty"` -} - func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { Authorize(w, r, authorizer) } } -func AuthorizeCallbackHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) { +func authorizeCallbackHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { AuthorizeCallback(w, r, authorizer) } @@ -95,29 +82,21 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { if authReq.RequestParam != "" && authorizer.RequestObjectSupported() { err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx)) if err != nil { - AuthRequestError(w, r, nil, err, authorizer) + AuthRequestError(w, r, authReq, err, authorizer) return } } if authReq.ClientID == "" { - AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing client_id"), authorizer) + AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing client_id"), authorizer) return } if authReq.RedirectURI == "" { - AuthRequestError(w, r, nil, fmt.Errorf("auth request is missing redirect_uri"), authorizer) + AuthRequestError(w, r, authReq, fmt.Errorf("auth request is missing redirect_uri"), authorizer) return } - - var client Client - validation := func(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) { - client, err = authorizer.Storage().GetClientByClientID(ctx, authReq.ClientID) - if err != nil { - return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err) - } - return ValidateAuthRequestClient(ctx, authReq, client, verifier) - } - if validator, ok := authorizer.(AuthorizeValidator); ok { - validation = validator.ValidateAuthRequest + validation := ValidateAuthRequest + if validater, ok := authorizer.(AuthorizeValidator); ok { + validation = validater.ValidateAuthRequest } userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx)) if err != nil { @@ -133,6 +112,11 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) { AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer) return } + client, err := authorizer.Storage().GetClientByClientID(ctx, req.GetClientID()) + if err != nil { + AuthRequestError(w, r, req, oidc.DefaultToServerError(err, "unable to retrieve client by id"), authorizer) + return + } RedirectToLogin(req.GetID(), client, w, r) } @@ -168,7 +152,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage if requestObject.Issuer != requestObject.ClientID { return oidc.ErrInvalidRequest().WithDescription("missing or wrong issuer in request") } - if !slices.Contains(requestObject.Audience, issuer) { + if !str.Contains(requestObject.Audience, issuer) { return oidc.ErrInvalidRequest().WithDescription("issuer missing in audience") } keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer} @@ -182,7 +166,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage // CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request // and clears the `RequestParam` of the auth request func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) { - if slices.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 { + if str.Contains(authReq.Scopes, oidc.ScopeOpenID) && len(requestObject.Scopes) > 0 { authReq.Scopes = requestObject.Scopes } if requestObject.RedirectURI != "" { @@ -227,37 +211,26 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi authReq.RequestParam = "" } -// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed. -// -// Deprecated: Use [ValidateAuthRequestClient] to prevent querying for the Client twice. +// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) { ctx, span := tracer.Start(ctx, "ValidateAuthRequest") defer span.End() - client, err := storage.GetClientByClientID(ctx, authReq.ClientID) - if err != nil { - return "", oidc.ErrInvalidRequestRedirectURI().WithDescription("unable to retrieve client by id").WithParent(err) - } - return ValidateAuthRequestClient(ctx, authReq, client, verifier) -} - -// ValidateAuthRequestClient validates the Auth request against the passed client. -// If id_token_hint is part of the request, the subject of the token is returned. -func ValidateAuthRequestClient(ctx context.Context, authReq *oidc.AuthRequest, client Client, verifier *IDTokenHintVerifier) (sub string, err error) { - ctx, span := tracer.Start(ctx, "ValidateAuthRequestClient") - defer span.End() - - if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil { - return "", err - } authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge) if err != nil { return "", err } + client, err := storage.GetClientByClientID(ctx, authReq.ClientID) + if err != nil { + return "", oidc.DefaultToServerError(err, "unable to retrieve client by id") + } authReq.Scopes, err = ValidateAuthReqScopes(client, authReq.Scopes) if err != nil { return "", err } + if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil { + return "", err + } if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil { return "", err } @@ -277,30 +250,44 @@ func ValidateAuthReqPrompt(prompts []string, maxAge *uint) (_ *uint, err error) return maxAge, nil } -// ValidateAuthReqScopes validates the passed scopes and deletes any unsupported scopes. -// An error is returned if scopes is empty. +// ValidateAuthReqScopes validates the passed scopes func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) { if len(scopes) == 0 { return nil, oidc.ErrInvalidRequest(). WithDescription("The scope of your request is missing. Please ensure some scopes are requested. " + "If you have any questions, you may contact the administrator of the application.") } - scopes = slices.DeleteFunc(scopes, func(scope string) bool { - return !(scope == oidc.ScopeOpenID || - scope == oidc.ScopeProfile || + openID := false + for i := len(scopes) - 1; i >= 0; i-- { + scope := scopes[i] + if scope == oidc.ScopeOpenID { + openID = true + continue + } + if !(scope == oidc.ScopeProfile || scope == oidc.ScopeEmail || scope == oidc.ScopePhone || scope == oidc.ScopeAddress || scope == oidc.ScopeOfflineAccess) && - !client.IsScopeAllowed(scope) - }) + !client.IsScopeAllowed(scope) { + scopes[i] = scopes[len(scopes)-1] + scopes[len(scopes)-1] = "" + scopes = scopes[:len(scopes)-1] + } + } + if !openID { + return nil, oidc.ErrInvalidScope().WithDescription("The scope openid is missing in your request. " + + "Please ensure the scope openid is added to the request. " + + "If you have any questions, you may contact the administrator of the application.") + } + return scopes, nil } // checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores // other factors. func checkURIAgainstRedirects(client Client, uri string) error { - if slices.Contains(client.RedirectURIs(), uri) { + if str.Contains(client.RedirectURIs(), uri) { return nil } if globClient, ok := client.(HasRedirectGlobs); ok { @@ -325,12 +312,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res return oidc.ErrInvalidRequestRedirectURI().WithDescription("The redirect_uri is missing in the request. " + "Please ensure it is added to the request. If you have any questions, you may contact the administrator of the application.") } - if client.ApplicationType() == ApplicationTypeNative { - return validateAuthReqRedirectURINative(client, uri) - } if strings.HasPrefix(uri, "https://") { return checkURIAgainstRedirects(client, uri) } + if client.ApplicationType() == ApplicationTypeNative { + return validateAuthReqRedirectURINative(client, uri) + } if err := checkURIAgainstRedirects(client, uri); err != nil { return err } @@ -351,15 +338,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res // ValidateAuthReqRedirectURINative validates the passed redirect_uri and response_type to the registered uris and client type func validateAuthReqRedirectURINative(client Client, uri string) error { parsedURL, isLoopback := HTTPLoopbackOrLocalhost(uri) - isCustomSchema := !(strings.HasPrefix(uri, "http://") || strings.HasPrefix(uri, "https://")) + isCustomSchema := !strings.HasPrefix(uri, "http://") if err := checkURIAgainstRedirects(client, uri); err == nil { if client.DevMode() { return nil } - if !isLoopback && strings.HasPrefix(uri, "https://") { - return nil - } - // The RedirectURIs are only valid for native clients when localhost or non-"http://" and "https://" + // The RedirectURIs are only valid for native clients when localhost or non-"http://" if isLoopback || isCustomSchema { return nil } @@ -389,11 +373,11 @@ func HTTPLoopbackOrLocalhost(rawURL string) (*url.URL, bool) { if err != nil { return nil, false } - if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" { - hostName := parsedURL.Hostname() - return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback() + if parsedURL.Scheme != "http" { + return nil, false } - return nil, false + hostName := parsedURL.Hostname() + return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback() } // ValidateAuthReqResponseType validates the passed response_type to the registered response types @@ -483,70 +467,41 @@ func AuthResponse(authReq AuthRequest, authorizer Authorizer, w http.ResponseWri AuthResponseToken(w, r, authReq, authorizer, client) } -// AuthResponseCode handles the creation of a successful authentication response using an authorization code +// AuthResponseCode creates the successful code authentication response func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) { ctx, span := tracer.Start(r.Context(), "AuthResponseCode") - defer span.End() r = r.WithContext(ctx) + defer span.End() - var err error - if authReq.GetResponseMode() == oidc.ResponseModeFormPost { - err = handleFormPostResponse(w, r, authReq, authorizer) - } else { - err = handleRedirectResponse(w, r, authReq, authorizer) - } - + code, err := CreateAuthRequestCode(r.Context(), authReq, authorizer.Storage(), authorizer.Crypto()) if err != nil { AuthRequestError(w, r, authReq, err, authorizer) + return + } + codeResponse := struct { + Code string `schema:"code"` + State string `schema:"state,omitempty"` + }{ + Code: code, + State: authReq.GetState(), } -} -// handleFormPostResponse processes the authentication response using form post method -func handleFormPostResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error { - codeResponse, err := BuildAuthResponseCodeResponsePayload(r.Context(), authReq, authorizer) + if authReq.GetResponseMode() == oidc.ResponseModeFormPost { + err := AuthResponseFormPost(w, authReq.GetRedirectURI(), &codeResponse, authorizer.Encoder()) + if err != nil { + AuthRequestError(w, r, authReq, err, authorizer) + return + } + + return + } + + callback, err := AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), &codeResponse, authorizer.Encoder()) if err != nil { - return err + AuthRequestError(w, r, authReq, err, authorizer) + return } - return AuthResponseFormPost(w, authReq.GetRedirectURI(), codeResponse, authorizer.Encoder()) -} - -// handleRedirectResponse processes the authentication response using the redirect method -func handleRedirectResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error { - callbackURL, err := BuildAuthResponseCallbackURL(r.Context(), authReq, authorizer) - if err != nil { - return err - } - http.Redirect(w, r, callbackURL, http.StatusFound) - return nil -} - -// BuildAuthResponseCodeResponsePayload generates the authorization code response payload for the authentication request -func BuildAuthResponseCodeResponsePayload(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (*CodeResponseType, error) { - code, err := CreateAuthRequestCode(ctx, authReq, authorizer.Storage(), authorizer.Crypto()) - if err != nil { - return nil, err - } - - sessionState := "" - if authRequestSessionState, ok := authReq.(AuthRequestSessionState); ok { - sessionState = authRequestSessionState.GetSessionState() - } - - return &CodeResponseType{ - Code: code, - State: authReq.GetState(), - SessionState: sessionState, - }, nil -} - -// BuildAuthResponseCallbackURL generates the callback URL for a successful authorization code response -func BuildAuthResponseCallbackURL(ctx context.Context, authReq AuthRequest, authorizer Authorizer) (string, error) { - codeResponse, err := BuildAuthResponseCodeResponsePayload(ctx, authReq, authorizer) - if err != nil { - return "", err - } - - return AuthResponseURL(authReq.GetRedirectURI(), authReq.GetResponseType(), authReq.GetResponseMode(), codeResponse, authorizer.Encoder()) + http.Redirect(w, r, callback, http.StatusFound) } // AuthResponseToken creates the successful token(s) authentication response diff --git a/pkg/op/auth_request_test.go b/pkg/op/auth_request_test.go index d1ea965..46d532e 100644 --- a/pkg/op/auth_request_test.go +++ b/pkg/op/auth_request_test.go @@ -11,15 +11,15 @@ import ( "reflect" "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/example/server/storage" + tu "github.com/zitadel/oidc/v4/internal/testutil" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" + "github.com/zitadel/oidc/v4/pkg/op/mock" "github.com/zitadel/schema" ) @@ -137,6 +137,11 @@ func TestValidateAuthRequest(t *testing.T) { args{&oidc.AuthRequest{}, mock.NewMockStorageExpectValidClientID(t), nil}, oidc.ErrInvalidRequest(), }, + { + "scope openid missing fails", + args{&oidc.AuthRequest{Scopes: []string{"profile"}}, mock.NewMockStorageExpectValidClientID(t), nil}, + oidc.ErrInvalidScope(), + }, { "response_type missing fails", args{&oidc.AuthRequest{Scopes: []string{"openid"}}, mock.NewMockStorageExpectValidClientID(t), nil}, @@ -282,6 +287,16 @@ func TestValidateAuthReqScopes(t *testing.T) { err: true, }, }, + { + "scope openid missing fails", + args{ + mock.NewClientExpectAny(t, op.ApplicationTypeWeb), + []string{"email"}, + }, + res{ + err: true, + }, + }, { "scope ok", args{ @@ -433,24 +448,6 @@ func TestValidateAuthReqRedirectURI(t *testing.T) { }, false, }, - { - "code flow registered https loopback v4 native ok", - args{ - "https://127.0.0.1:4200/callback", - mock.NewClientWithConfig(t, []string{"https://127.0.0.1/callback"}, op.ApplicationTypeNative, nil, false), - oidc.ResponseTypeCode, - }, - false, - }, - { - "code flow registered https loopback v6 native ok", - args{ - "https://[::1]:4200/callback", - mock.NewClientWithConfig(t, []string{"https://[::1]/callback"}, op.ApplicationTypeNative, nil, false), - oidc.ResponseTypeCode, - }, - false, - }, { "code flow unregistered http native fails", args{ @@ -1090,34 +1087,6 @@ func TestAuthResponseCode(t *testing.T) { wantBody: "", }, }, - { - name: "success with state and session_state", - args: args{ - authReq: &storage.AuthRequestWithSessionState{ - AuthRequest: &storage.AuthRequest{ - ID: "id1", - TransferState: "state1", - }, - SessionState: "session_state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantCode: http.StatusFound, - wantLocationHeader: "/auth/callback/?code=id1&session_state=session_state1&state=state1", - wantBody: "", - }, - }, { name: "success without state", // reproduce issue #415 args: args{ @@ -1225,133 +1194,6 @@ func Test_parseAuthorizeCallbackRequest(t *testing.T) { } } -func TestBuildAuthResponseCodeResponsePayload(t *testing.T) { - type args struct { - authReq op.AuthRequest - authorizer func(*testing.T) op.Authorizer - } - type res struct { - wantCode string - wantState string - wantSessionState string - wantErr bool - } - tests := []struct { - name string - args args - res res - }{ - { - name: "create code error", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{ - returnErr: io.ErrClosedPipe, - }) - return authorizer - }, - }, - res: res{ - wantErr: true, - }, - }, - { - name: "success with state", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - TransferState: "state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - return authorizer - }, - }, - res: res{ - wantCode: "id1", - wantState: "state1", - }, - }, - { - name: "success without state", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - TransferState: "", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - return authorizer - }, - }, - res: res{ - wantCode: "id1", - wantState: "", - }, - }, - { - name: "success with session_state", - args: args{ - authReq: &storage.AuthRequestWithSessionState{ - AuthRequest: &storage.AuthRequest{ - ID: "id1", - TransferState: "state1", - }, - SessionState: "session_state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - return authorizer - }, - }, - res: res{ - wantCode: "id1", - wantState: "state1", - wantSessionState: "session_state1", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := op.BuildAuthResponseCodeResponsePayload(context.Background(), tt.args.authReq, tt.args.authorizer(t)) - if tt.res.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - assert.Equal(t, tt.res.wantCode, got.Code) - assert.Equal(t, tt.res.wantState, got.State) - assert.Equal(t, tt.res.wantSessionState, got.SessionState) - }) - } -} - func TestValidateAuthReqIDTokenHint(t *testing.T) { token, _ := tu.ValidIDToken() tests := []struct { @@ -1382,231 +1224,3 @@ func TestValidateAuthReqIDTokenHint(t *testing.T) { }) } } - -func TestBuildAuthResponseCallbackURL(t *testing.T) { - type args struct { - authReq op.AuthRequest - authorizer func(*testing.T) op.Authorizer - } - type res struct { - wantURL string - wantErr bool - } - tests := []struct { - name string - args args - res res - }{ - { - name: "error when generating code response", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{ - returnErr: io.ErrClosedPipe, - }) - return authorizer - }, - }, - res: res{ - wantErr: true, - }, - }, - { - name: "error when generating callback URL", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "://invalid-url", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantErr: true, - }, - }, - { - name: "success with state", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "https://example.com/callback", - TransferState: "state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantURL: "https://example.com/callback?code=id1&state=state1", - wantErr: false, - }, - }, - { - name: "success without state", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "https://example.com/callback", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantURL: "https://example.com/callback?code=id1", - wantErr: false, - }, - }, - { - name: "success with session_state", - args: args{ - authReq: &storage.AuthRequestWithSessionState{ - AuthRequest: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "https://example.com/callback", - TransferState: "state1", - }, - SessionState: "session_state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantURL: "https://example.com/callback?code=id1&session_state=session_state1&state=state1", - wantErr: false, - }, - }, - { - name: "success with existing query parameters", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "https://example.com/callback?param=value", - TransferState: "state1", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantURL: "https://example.com/callback?param=value&code=id1&state=state1", - wantErr: false, - }, - }, - { - name: "success with fragment response mode", - args: args{ - authReq: &storage.AuthRequest{ - ID: "id1", - CallbackURI: "https://example.com/callback", - TransferState: "state1", - ResponseMode: "fragment", - }, - authorizer: func(t *testing.T) op.Authorizer { - ctrl := gomock.NewController(t) - storage := mock.NewMockStorage(ctrl) - storage.EXPECT().SaveAuthCode(gomock.Any(), "id1", "id1") - - authorizer := mock.NewMockAuthorizer(ctrl) - authorizer.EXPECT().Storage().Return(storage) - authorizer.EXPECT().Crypto().Return(&mockCrypto{}) - authorizer.EXPECT().Encoder().Return(schema.NewEncoder()) - return authorizer - }, - }, - res: res{ - wantURL: "https://example.com/callback#code=id1&state=state1", - wantErr: false, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := op.BuildAuthResponseCallbackURL(context.Background(), tt.args.authReq, tt.args.authorizer(t)) - if tt.res.wantErr { - assert.Error(t, err) - return - } - require.NoError(t, err) - - if tt.res.wantURL != "" { - // Parse the URLs to compare components instead of direct string comparison - expectedURL, err := url.Parse(tt.res.wantURL) - require.NoError(t, err) - actualURL, err := url.Parse(got) - require.NoError(t, err) - - // Compare the base parts (scheme, host, path) - assert.Equal(t, expectedURL.Scheme, actualURL.Scheme) - assert.Equal(t, expectedURL.Host, actualURL.Host) - assert.Equal(t, expectedURL.Path, actualURL.Path) - - // Compare the fragment if any - assert.Equal(t, expectedURL.Fragment, actualURL.Fragment) - - // For query parameters, compare them independently of order - expectedQuery := expectedURL.Query() - actualQuery := actualURL.Query() - - assert.Equal(t, len(expectedQuery), len(actualQuery), "Query parameter count does not match") - - for key, expectedValues := range expectedQuery { - actualValues, exists := actualQuery[key] - assert.True(t, exists, "Expected query parameter %s not found", key) - assert.ElementsMatch(t, expectedValues, actualValues, "Values for parameter %s don't match", key) - } - } - }) - } -} diff --git a/pkg/op/client.go b/pkg/op/client.go index a4f44d3..f2e470b 100644 --- a/pkg/op/client.go +++ b/pkg/op/client.go @@ -7,8 +7,8 @@ import ( "net/url" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) //go:generate go get github.com/dmarkham/enumer diff --git a/pkg/op/client_test.go b/pkg/op/client_test.go index b416630..3a129bf 100644 --- a/pkg/op/client_test.go +++ b/pkg/op/client_test.go @@ -10,13 +10,13 @@ import ( "strings" "testing" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" + "github.com/zitadel/oidc/v4/pkg/op/mock" "github.com/zitadel/schema" ) diff --git a/pkg/op/config.go b/pkg/op/config.go index b271765..9fec7cc 100644 --- a/pkg/op/config.go +++ b/pkg/op/config.go @@ -30,7 +30,6 @@ type Configuration interface { EndSessionEndpoint() *Endpoint KeysEndpoint() *Endpoint DeviceAuthorizationEndpoint() *Endpoint - CheckSessionIframe() *Endpoint AuthMethodPostSupported() bool CodeMethodS256Supported() bool @@ -50,9 +49,6 @@ type Configuration interface { SupportedUILocales() []language.Tag DeviceAuthorization() DeviceAuthorizationConfig - - BackChannelLogoutSupported() bool - BackChannelLogoutSessionSupported() bool } type IssuerFromRequest func(r *http.Request) string diff --git a/pkg/op/crypto.go b/pkg/op/crypto.go index 01aaad3..6c3357c 100644 --- a/pkg/op/crypto.go +++ b/pkg/op/crypto.go @@ -1,7 +1,7 @@ package op import ( - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" + "github.com/zitadel/oidc/v4/pkg/crypto" ) type Crypto interface { diff --git a/pkg/op/device.go b/pkg/op/device.go index 866cbc4..bf8a344 100644 --- a/pkg/op/device.go +++ b/pkg/op/device.go @@ -9,12 +9,12 @@ import ( "math/big" "net/http" "net/url" - "slices" "strings" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + strs "github.com/zitadel/oidc/v4/pkg/strings" ) type DeviceAuthorizationConfig struct { @@ -91,7 +91,10 @@ func createDeviceAuthorization(ctx context.Context, req *oidc.DeviceAuthorizatio } config := o.DeviceAuthorization() - deviceCode, _ := NewDeviceCode(RecommendedDeviceCodeBytes) + deviceCode, err := NewDeviceCode(RecommendedDeviceCodeBytes) + if err != nil { + return nil, NewStatusError(err, http.StatusInternalServerError) + } userCode, err := NewUserCode([]rune(config.UserCode.CharSet), config.UserCode.CharAmount, config.UserCode.DashInterval) if err != nil { return nil, NewStatusError(err, http.StatusInternalServerError) @@ -160,14 +163,11 @@ func ParseDeviceCodeRequest(r *http.Request, o OpenIDProvider) (*oidc.DeviceAuth // results in a 22 character base64 encoded string. const RecommendedDeviceCodeBytes = 16 -// NewDeviceCode generates a new cryptographically secure device code as a base64 encoded string. -// The length of the string is nBytes * 4 / 3. -// An error is never returned. -// -// TODO(v4): change return type to string alone. func NewDeviceCode(nBytes int) (string, error) { bytes := make([]byte, nBytes) - rand.Read(bytes) + if _, err := rand.Read(bytes); err != nil { + return "", fmt.Errorf("%w getting entropy for device code", err) + } return base64.RawURLEncoding.EncodeToString(bytes), nil } @@ -276,7 +276,7 @@ func (r *DeviceAuthorizationState) GetAMR() []string { } func (r *DeviceAuthorizationState) GetAudience() []string { - if !slices.Contains(r.Audience, r.ClientID) { + if !strs.Contains(r.Audience, r.ClientID) { r.Audience = append(r.Audience, r.ClientID) } return r.Audience @@ -344,11 +344,10 @@ func CreateDeviceTokenResponse(ctx context.Context, tokenRequest TokenRequest, c RefreshToken: refreshToken, TokenType: oidc.BearerToken, ExpiresIn: uint64(validity.Seconds()), - Scope: tokenRequest.GetScopes(), } // TODO(v4): remove type assertion - if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && slices.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) { + if idTokenRequest, ok := tokenRequest.(IDTokenRequest); ok && strs.Contains(tokenRequest.GetScopes(), oidc.ScopeOpenID) { response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client) if err != nil { return nil, err diff --git a/pkg/op/device_test.go b/pkg/op/device_test.go index a7b5c4e..686c833 100644 --- a/pkg/op/device_test.go +++ b/pkg/op/device_test.go @@ -13,12 +13,12 @@ import ( "testing" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/example/server/storage" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func Test_deviceAuthorizationHandler(t *testing.T) { @@ -145,11 +145,21 @@ func runWithRandReader(r io.Reader, f func()) { } func TestNewDeviceCode(t *testing.T) { - for i := 1; i <= 32; i++ { - got, err := op.NewDeviceCode(i) - require.NoError(t, err) - assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i)) - } + t.Run("reader error", func(t *testing.T) { + runWithRandReader(errReader{}, func() { + _, err := op.NewDeviceCode(16) + require.Error(t, err) + }) + }) + + t.Run("different lengths, rand reader", func(t *testing.T) { + for i := 1; i <= 32; i++ { + got, err := op.NewDeviceCode(i) + require.NoError(t, err) + assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i)) + } + }) + } func TestNewUserCode(t *testing.T) { diff --git a/pkg/op/discovery.go b/pkg/op/discovery.go index 9b3ddb6..07c52e9 100644 --- a/pkg/op/discovery.go +++ b/pkg/op/discovery.go @@ -4,10 +4,10 @@ import ( "context" "net/http" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type DiscoverStorage interface { @@ -45,7 +45,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer), JwksURI: config.KeysEndpoint().Absolute(issuer), DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer), - CheckSessionIframe: config.CheckSessionIframe().Absolute(issuer), ScopesSupported: Scopes(config), ResponseTypesSupported: ResponseTypes(config), GrantTypesSupported: GrantTypes(config), @@ -62,8 +61,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di CodeChallengeMethodsSupported: CodeChallengeMethods(config), UILocalesSupported: config.SupportedUILocales(), RequestParameterSupported: config.RequestObjectSupported(), - BackChannelLogoutSupported: config.BackChannelLogoutSupported(), - BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(), } } @@ -95,17 +92,11 @@ func createDiscoveryConfigV2(ctx context.Context, config Configuration, storage CodeChallengeMethodsSupported: CodeChallengeMethods(config), UILocalesSupported: config.SupportedUILocales(), RequestParameterSupported: config.RequestObjectSupported(), - BackChannelLogoutSupported: config.BackChannelLogoutSupported(), - BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(), } } func Scopes(c Configuration) []string { - provider, ok := c.(*Provider) - if ok && provider.config.SupportedScopes != nil { - return provider.config.SupportedScopes - } - return DefaultSupportedScopes + return DefaultSupportedScopes // TODO: config } func ResponseTypes(c Configuration) []string { @@ -140,7 +131,7 @@ func GrantTypes(c Configuration) []oidc.GrantType { } func SubjectTypes(c Configuration) []string { - return []string{"public"} // TODO: config + return []string{"public"} //TODO: config } func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string { diff --git a/pkg/op/discovery_test.go b/pkg/op/discovery_test.go index 63f1b98..3637a9f 100644 --- a/pkg/op/discovery_test.go +++ b/pkg/op/discovery_test.go @@ -6,14 +6,14 @@ import ( "net/http/httptest" "testing" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" + "github.com/zitadel/oidc/v4/pkg/op/mock" ) func TestDiscover(t *testing.T) { @@ -81,11 +81,6 @@ func Test_scopes(t *testing.T) { args{}, op.DefaultSupportedScopes, }, - { - "custom scopes", - args{newTestProvider(&op.Config{SupportedScopes: []string{"test1", "test2"}})}, - []string{"test1", "test2"}, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/op/endpoint_test.go b/pkg/op/endpoint_test.go index 5b98c6e..3c04eb4 100644 --- a/pkg/op/endpoint_test.go +++ b/pkg/op/endpoint_test.go @@ -3,8 +3,8 @@ package op_test import ( "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/pkg/op" ) func TestEndpoint_Path(t *testing.T) { diff --git a/pkg/op/error.go b/pkg/op/error.go index 272f85e..99f203b 100644 --- a/pkg/op/error.go +++ b/pkg/op/error.go @@ -7,8 +7,8 @@ import ( "log/slog" "net/http" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type ErrAuthRequest interface { @@ -46,12 +46,6 @@ func AuthRequestError(w http.ResponseWriter, r *http.Request, authReq ErrAuthReq return } e.State = authReq.GetState() - var sessionState string - authRequestSessionState, ok := authReq.(AuthRequestSessionState) - if ok { - sessionState = authRequestSessionState.GetSessionState() - } - e.SessionState = sessionState var responseMode oidc.ResponseMode if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok { responseMode = rm.GetResponseMode() @@ -98,12 +92,6 @@ func TryErrorRedirect(ctx context.Context, authReq ErrAuthRequest, parent error, } e.State = authReq.GetState() - var sessionState string - authRequestSessionState, ok := authReq.(AuthRequestSessionState) - if ok { - sessionState = authRequestSessionState.GetSessionState() - } - e.SessionState = sessionState var responseMode oidc.ResponseMode if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok { responseMode = rm.GetResponseMode() diff --git a/pkg/op/error_test.go b/pkg/op/error_test.go index 9271cf1..b5c40b3 100644 --- a/pkg/op/error_test.go +++ b/pkg/op/error_test.go @@ -11,9 +11,9 @@ import ( "strings" "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/pkg/oidc" "github.com/zitadel/schema" ) @@ -428,8 +428,7 @@ func TestTryErrorRedirect(t *testing.T) { parent: oidc.ErrInteractionRequired().WithDescription("sign in"), }, want: &Redirect{ - Header: make(http.Header), - URL: "http://example.com/callback?error=interaction_required&error_description=sign+in&state=state1", + URL: "http://example.com/callback?error=interaction_required&error_description=sign+in&state=state1", }, wantLog: `{ "level":"WARN", diff --git a/pkg/op/keys.go b/pkg/op/keys.go index 97e400b..bd217b8 100644 --- a/pkg/op/keys.go +++ b/pkg/op/keys.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" + httphelper "github.com/zitadel/oidc/v4/pkg/http" ) type KeyProvider interface { diff --git a/pkg/op/keys_test.go b/pkg/op/keys_test.go index 9c80878..2ca3f49 100644 --- a/pkg/op/keys_test.go +++ b/pkg/op/keys_test.go @@ -7,13 +7,13 @@ import ( "net/http/httptest" "testing" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" + "github.com/zitadel/oidc/v4/pkg/op/mock" ) func TestKeys(t *testing.T) { diff --git a/pkg/op/mock/authorizer.mock.go b/pkg/op/mock/authorizer.mock.go index 56b28e0..a7cc1c4 100644 --- a/pkg/op/mock/authorizer.mock.go +++ b/pkg/op/mock/authorizer.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Authorizer) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Authorizer) // Package mock is a generated GoMock package. package mock @@ -9,9 +9,9 @@ import ( slog "log/slog" reflect "reflect" - http "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + http "github.com/zitadel/oidc/v4/pkg/http" + op "github.com/zitadel/oidc/v4/pkg/op" ) // MockAuthorizer is a mock of Authorizer interface. diff --git a/pkg/op/mock/authorizer.mock.impl.go b/pkg/op/mock/authorizer.mock.impl.go index 73c4154..af75ec3 100644 --- a/pkg/op/mock/authorizer.mock.impl.go +++ b/pkg/op/mock/authorizer.mock.impl.go @@ -4,12 +4,12 @@ import ( "context" "testing" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/golang/mock/gomock" "github.com/zitadel/schema" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func NewAuthorizer(t *testing.T) op.Authorizer { diff --git a/pkg/op/mock/client.go b/pkg/op/mock/client.go index e2a5e85..dc97e26 100644 --- a/pkg/op/mock/client.go +++ b/pkg/op/mock/client.go @@ -5,8 +5,8 @@ import ( "github.com/golang/mock/gomock" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func NewClient(t *testing.T) op.Client { diff --git a/pkg/op/mock/client.mock.go b/pkg/op/mock/client.mock.go index 93eca67..2600760 100644 --- a/pkg/op/mock/client.mock.go +++ b/pkg/op/mock/client.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Client) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Client) // Package mock is a generated GoMock package. package mock @@ -8,9 +8,9 @@ import ( reflect "reflect" time "time" - oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + oidc "github.com/zitadel/oidc/v4/pkg/oidc" + op "github.com/zitadel/oidc/v4/pkg/op" ) // MockClient is a mock of Client interface. diff --git a/pkg/op/mock/configuration.mock.go b/pkg/op/mock/configuration.mock.go index bf51035..7b03e8e 100644 --- a/pkg/op/mock/configuration.mock.go +++ b/pkg/op/mock/configuration.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Configuration) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Configuration) // Package mock is a generated GoMock package. package mock @@ -8,8 +8,8 @@ import ( http "net/http" reflect "reflect" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + op "github.com/zitadel/oidc/v4/pkg/op" language "golang.org/x/text/language" ) @@ -78,48 +78,6 @@ func (mr *MockConfigurationMockRecorder) AuthorizationEndpoint() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthorizationEndpoint", reflect.TypeOf((*MockConfiguration)(nil).AuthorizationEndpoint)) } -// BackChannelLogoutSessionSupported mocks base method. -func (m *MockConfiguration) BackChannelLogoutSessionSupported() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BackChannelLogoutSessionSupported") - ret0, _ := ret[0].(bool) - return ret0 -} - -// BackChannelLogoutSessionSupported indicates an expected call of BackChannelLogoutSessionSupported. -func (mr *MockConfigurationMockRecorder) BackChannelLogoutSessionSupported() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSessionSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSessionSupported)) -} - -// BackChannelLogoutSupported mocks base method. -func (m *MockConfiguration) BackChannelLogoutSupported() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BackChannelLogoutSupported") - ret0, _ := ret[0].(bool) - return ret0 -} - -// BackChannelLogoutSupported indicates an expected call of BackChannelLogoutSupported. -func (mr *MockConfigurationMockRecorder) BackChannelLogoutSupported() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BackChannelLogoutSupported", reflect.TypeOf((*MockConfiguration)(nil).BackChannelLogoutSupported)) -} - -// CheckSessionIframe mocks base method. -func (m *MockConfiguration) CheckSessionIframe() *op.Endpoint { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckSessionIframe") - ret0, _ := ret[0].(*op.Endpoint) - return ret0 -} - -// CheckSessionIframe indicates an expected call of CheckSessionIframe. -func (mr *MockConfigurationMockRecorder) CheckSessionIframe() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckSessionIframe", reflect.TypeOf((*MockConfiguration)(nil).CheckSessionIframe)) -} - // CodeMethodS256Supported mocks base method. func (m *MockConfiguration) CodeMethodS256Supported() bool { m.ctrl.T.Helper() diff --git a/pkg/op/mock/discovery.mock.go b/pkg/op/mock/discovery.mock.go index c85f91b..d524283 100644 --- a/pkg/op/mock/discovery.mock.go +++ b/pkg/op/mock/discovery.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: DiscoverStorage) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: DiscoverStorage) // Package mock is a generated GoMock package. package mock @@ -8,7 +8,7 @@ import ( context "context" reflect "reflect" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" gomock "github.com/golang/mock/gomock" ) diff --git a/pkg/op/mock/generate.go b/pkg/op/mock/generate.go index 3d58ab7..1ae819f 100644 --- a/pkg/op/mock/generate.go +++ b/pkg/op/mock/generate.go @@ -1,11 +1,11 @@ package mock //go:generate go install github.com/golang/mock/mockgen@v1.6.0 -//go:generate mockgen -package mock -destination ./storage.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Storage -//go:generate mockgen -package mock -destination ./authorizer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Authorizer -//go:generate mockgen -package mock -destination ./client.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Client -//go:generate mockgen -package mock -destination ./glob.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op HasRedirectGlobs -//go:generate mockgen -package mock -destination ./configuration.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op Configuration -//go:generate mockgen -package mock -destination ./discovery.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op DiscoverStorage -//go:generate mockgen -package mock -destination ./signer.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op SigningKey,Key -//go:generate mockgen -package mock -destination ./key.mock.go git.christmann.info/LARA/zitadel-oidc/v3/pkg/op KeyProvider +//go:generate mockgen -package mock -destination ./storage.mock.go github.com/zitadel/oidc/v4/pkg/op Storage +//go:generate mockgen -package mock -destination ./authorizer.mock.go github.com/zitadel/oidc/v4/pkg/op Authorizer +//go:generate mockgen -package mock -destination ./client.mock.go github.com/zitadel/oidc/v4/pkg/op Client +//go:generate mockgen -package mock -destination ./glob.mock.go github.com/zitadel/oidc/v4/pkg/op HasRedirectGlobs +//go:generate mockgen -package mock -destination ./configuration.mock.go github.com/zitadel/oidc/v4/pkg/op Configuration +//go:generate mockgen -package mock -destination ./discovery.mock.go github.com/zitadel/oidc/v4/pkg/op DiscoverStorage +//go:generate mockgen -package mock -destination ./signer.mock.go github.com/zitadel/oidc/v4/pkg/op SigningKey,Key +//go:generate mockgen -package mock -destination ./key.mock.go github.com/zitadel/oidc/v4/pkg/op KeyProvider diff --git a/pkg/op/mock/glob.go b/pkg/op/mock/glob.go index 8149c8f..118ac91 100644 --- a/pkg/op/mock/glob.go +++ b/pkg/op/mock/glob.go @@ -3,9 +3,9 @@ package mock import ( "testing" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + "github.com/zitadel/oidc/v4/pkg/oidc" + op "github.com/zitadel/oidc/v4/pkg/op" ) func NewHasRedirectGlobs(t *testing.T) op.HasRedirectGlobs { diff --git a/pkg/op/mock/glob.mock.go b/pkg/op/mock/glob.mock.go index ebdc333..07bf0fa 100644 --- a/pkg/op/mock/glob.mock.go +++ b/pkg/op/mock/glob.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: HasRedirectGlobs) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: HasRedirectGlobs) // Package mock is a generated GoMock package. package mock @@ -8,9 +8,9 @@ import ( reflect "reflect" time "time" - oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + oidc "github.com/zitadel/oidc/v4/pkg/oidc" + op "github.com/zitadel/oidc/v4/pkg/op" ) // MockHasRedirectGlobs is a mock of HasRedirectGlobs interface. diff --git a/pkg/op/mock/key.mock.go b/pkg/op/mock/key.mock.go index d9ee857..ce5bca2 100644 --- a/pkg/op/mock/key.mock.go +++ b/pkg/op/mock/key.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: KeyProvider) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: KeyProvider) // Package mock is a generated GoMock package. package mock @@ -8,8 +8,8 @@ import ( context "context" reflect "reflect" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" gomock "github.com/golang/mock/gomock" + op "github.com/zitadel/oidc/v4/pkg/op" ) // MockKeyProvider is a mock of KeyProvider interface. diff --git a/pkg/op/mock/signer.mock.go b/pkg/op/mock/signer.mock.go index 751ce60..d9ff4a9 100644 --- a/pkg/op/mock/signer.mock.go +++ b/pkg/op/mock/signer.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: SigningKey,Key) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: SigningKey,Key) // Package mock is a generated GoMock package. package mock @@ -7,7 +7,7 @@ package mock import ( reflect "reflect" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" gomock "github.com/golang/mock/gomock" ) diff --git a/pkg/op/mock/storage.mock.go b/pkg/op/mock/storage.mock.go index 0df9830..27f0848 100644 --- a/pkg/op/mock/storage.mock.go +++ b/pkg/op/mock/storage.mock.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Storage) +// Source: github.com/zitadel/oidc/v4/pkg/op (interfaces: Storage) // Package mock is a generated GoMock package. package mock @@ -9,10 +9,10 @@ import ( reflect "reflect" time "time" - oidc "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" gomock "github.com/golang/mock/gomock" + oidc "github.com/zitadel/oidc/v4/pkg/oidc" + op "github.com/zitadel/oidc/v4/pkg/op" ) // MockStorage is a mock of Storage interface. diff --git a/pkg/op/mock/storage.mock.impl.go b/pkg/op/mock/storage.mock.impl.go index 96e08a9..1b2aca8 100644 --- a/pkg/op/mock/storage.mock.impl.go +++ b/pkg/op/mock/storage.mock.impl.go @@ -8,8 +8,8 @@ import ( "github.com/golang/mock/gomock" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func NewStorage(t *testing.T) op.Storage { diff --git a/pkg/op/op.go b/pkg/op/op.go index 76c2c89..31de4f2 100644 --- a/pkg/op/op.go +++ b/pkg/op/op.go @@ -8,14 +8,14 @@ import ( "time" "github.com/go-chi/chi/v5" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" "github.com/rs/cors" "github.com/zitadel/schema" "go.opentelemetry.io/otel" "golang.org/x/text/language" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) const ( @@ -135,7 +135,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) chi.Router router.HandleFunc(readinessEndpoint, readyHandler(o.Probes())) router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Storage())) router.HandleFunc(o.AuthorizationEndpoint().Relative(), authorizeHandler(o)) - router.HandleFunc(authCallbackPath(o), AuthorizeCallbackHandler(o)) + router.HandleFunc(authCallbackPath(o), authorizeCallbackHandler(o)) router.HandleFunc(o.TokenEndpoint().Relative(), tokenHandler(o)) router.HandleFunc(o.IntrospectionEndpoint().Relative(), introspectionHandler(o)) router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o)) @@ -158,19 +158,16 @@ func authCallbackPath(o OpenIDProvider) string { } type Config struct { - CryptoKey [32]byte - DefaultLogoutRedirectURI string - CodeMethodS256 bool - AuthMethodPost bool - AuthMethodPrivateKeyJWT bool - GrantTypeRefreshToken bool - RequestObjectSupported bool - SupportedUILocales []language.Tag - SupportedClaims []string - SupportedScopes []string - DeviceAuthorization DeviceAuthorizationConfig - BackChannelLogoutSupported bool - BackChannelLogoutSessionSupported bool + CryptoKey [32]byte + DefaultLogoutRedirectURI string + CodeMethodS256 bool + AuthMethodPost bool + AuthMethodPrivateKeyJWT bool + GrantTypeRefreshToken bool + RequestObjectSupported bool + SupportedUILocales []language.Tag + SupportedClaims []string + DeviceAuthorization DeviceAuthorizationConfig } // Endpoints defines endpoint routes. @@ -339,10 +336,6 @@ func (o *Provider) DeviceAuthorizationEndpoint() *Endpoint { return o.endpoints.DeviceAuthorization } -func (o *Provider) CheckSessionIframe() *Endpoint { - return o.endpoints.CheckSessionIframe -} - func (o *Provider) KeysEndpoint() *Endpoint { return o.endpoints.JwksURI } @@ -418,14 +411,6 @@ func (o *Provider) DeviceAuthorization() DeviceAuthorizationConfig { return o.config.DeviceAuthorization } -func (o *Provider) BackChannelLogoutSupported() bool { - return o.config.BackChannelLogoutSupported -} - -func (o *Provider) BackChannelLogoutSessionSupported() bool { - return o.config.BackChannelLogoutSessionSupported -} - func (o *Provider) Storage() Storage { return o.storage } diff --git a/pkg/op/op_test.go b/pkg/op/op_test.go index e1ac0bd..b76972f 100644 --- a/pkg/op/op_test.go +++ b/pkg/op/op_test.go @@ -11,12 +11,12 @@ import ( "testing" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/zitadel/oidc/v4/example/server/storage" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" "golang.org/x/text/language" ) @@ -232,7 +232,7 @@ func TestRoutes(t *testing.T) { "scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(), }, wantCode: http.StatusOK, - contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299,"scope":"openid offline_access"}`}, + contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299}`}, }, { // This call will fail. A successful test is already diff --git a/pkg/op/probes.go b/pkg/op/probes.go index fa713da..0c51bac 100644 --- a/pkg/op/probes.go +++ b/pkg/op/probes.go @@ -5,7 +5,7 @@ import ( "errors" "net/http" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" + httphelper "github.com/zitadel/oidc/v4/pkg/http" ) type ProbesFn func(context.Context) error diff --git a/pkg/op/server.go b/pkg/op/server.go index d45b734..20ddc0d 100644 --- a/pkg/op/server.go +++ b/pkg/op/server.go @@ -5,9 +5,9 @@ import ( "net/http" "net/url" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/muhlemmer/gu" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // Server describes the interface that needs to be implemented to serve @@ -218,8 +218,7 @@ type Response struct { // without custom headers. func NewResponse(data any) *Response { return &Response{ - Header: make(http.Header), - Data: data, + Data: data, } } @@ -243,14 +242,11 @@ type Redirect struct { } func NewRedirect(url string) *Redirect { - return &Redirect{ - Header: make(http.Header), - URL: url, - } + return &Redirect{URL: url} } func (red *Redirect) writeOut(w http.ResponseWriter, r *http.Request) { - gu.MapMerge(red.Header, w.Header()) + gu.MapMerge(r.Header, w.Header()) http.Redirect(w, r, red.URL, http.StatusFound) } diff --git a/pkg/op/server_http.go b/pkg/op/server_http.go index d71a354..e3fd553 100644 --- a/pkg/op/server_http.go +++ b/pkg/op/server_http.go @@ -6,11 +6,11 @@ import ( "net/http" "net/url" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/go-chi/chi/v5" "github.com/rs/cors" "github.com/zitadel/logging" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" "github.com/zitadel/schema" ) diff --git a/pkg/op/server_http_routes_test.go b/pkg/op/server_http_routes_test.go index 02200ee..9b3e567 100644 --- a/pkg/op/server_http_routes_test.go +++ b/pkg/op/server_http_routes_test.go @@ -14,9 +14,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + "github.com/zitadel/oidc/v4/pkg/client" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func jwtProfile() (string, error) { @@ -32,7 +32,7 @@ func jwtProfile() (string, error) { } func TestServerRoutes(t *testing.T) { - server := op.RegisterLegacyServer(op.NewLegacyServer(testProvider, *op.DefaultEndpoints), op.AuthorizeCallbackHandler(testProvider)) + server := op.RegisterLegacyServer(op.NewLegacyServer(testProvider, *op.DefaultEndpoints)) storage := testProvider.Storage().(routesTestStorage) ctx := op.ContextWithIssuer(context.Background(), testIssuer) @@ -145,7 +145,7 @@ func TestServerRoutes(t *testing.T) { "assertion": jwtProfileToken, }, wantCode: http.StatusOK, - contains: []string{`{"access_token":`, `"token_type":"Bearer","expires_in":299,"scope":"openid"}`}, + contains: []string{`{"access_token":`, `"token_type":"Bearer","expires_in":299}`}, }, { name: "Token exchange", @@ -174,7 +174,7 @@ func TestServerRoutes(t *testing.T) { "scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(), }, wantCode: http.StatusOK, - contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299,"scope":"openid offline_access"}`}, + contains: []string{`{"access_token":"`, `","token_type":"Bearer","expires_in":299}`}, }, { // This call will fail. A successful test is already diff --git a/pkg/op/server_http_test.go b/pkg/op/server_http_test.go index 75d02ca..4225e6f 100644 --- a/pkg/op/server_http_test.go +++ b/pkg/op/server_http_test.go @@ -14,11 +14,11 @@ import ( "testing" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" "github.com/zitadel/schema" ) diff --git a/pkg/op/server_legacy.go b/pkg/op/server_legacy.go index 06e4e93..04bfd95 100644 --- a/pkg/op/server_legacy.go +++ b/pkg/op/server_legacy.go @@ -6,8 +6,8 @@ import ( "net/http" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/go-chi/chi/v5" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // ExtendedLegacyServer allows embedding [LegacyServer] in a struct, @@ -22,16 +22,17 @@ type ExtendedLegacyServer interface { } // RegisterLegacyServer registers a [LegacyServer] or an extension thereof. -// It takes care of registering the IssuerFromRequest middleware. -// The authorizeCallbackHandler is registered on `/callback` under the authorization endpoint. +// It takes care of registering the IssuerFromRequest middleware +// and Authorization Callback Routes. // Neither are part of the bare [Server] interface. // // EXPERIMENTAL: may change until v4 -func RegisterLegacyServer(s ExtendedLegacyServer, authorizeCallbackHandler http.HandlerFunc, options ...ServerOption) http.Handler { +func RegisterLegacyServer(s ExtendedLegacyServer, options ...ServerOption) http.Handler { + provider := s.Provider() options = append(options, - WithHTTPMiddleware(intercept(s.Provider().IssuerFromRequest)), + WithHTTPMiddleware(intercept(provider.IssuerFromRequest)), WithSetRouter(func(r chi.Router) { - r.HandleFunc(s.Endpoints().Authorization.Relative()+authCallbackPathSuffix, authorizeCallbackHandler) + r.HandleFunc(s.Endpoints().Authorization.Relative()+authCallbackPathSuffix, authorizeCallbackHandler(provider)) }), ) return RegisterServer(s, s.Endpoints(), options...) diff --git a/pkg/op/session.go b/pkg/op/session.go index ac663c9..1985667 100644 --- a/pkg/op/session.go +++ b/pkg/op/session.go @@ -8,8 +8,8 @@ import ( "net/url" "path" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type SessionEnder interface { @@ -73,8 +73,6 @@ func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest, session := &EndSessionRequest{ RedirectURI: ender.DefaultLogoutRedirectURI(), - LogoutHint: req.LogoutHint, - UILocales: req.UILocales, } if req.IdTokenHint != "" { claims, err := VerifyIDTokenHint[*oidc.IDTokenClaims](ctx, req.IdTokenHint, ender.IDTokenHintVerifier(ctx)) diff --git a/pkg/op/signer.go b/pkg/op/signer.go index 5c3dd6a..b220739 100644 --- a/pkg/op/signer.go +++ b/pkg/op/signer.go @@ -3,7 +3,7 @@ package op import ( "errors" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" ) var ErrSignerCreationFailed = errors.New("signer creation failed") diff --git a/pkg/op/storage.go b/pkg/op/storage.go index 2dbd124..a3ee7bc 100644 --- a/pkg/op/storage.go +++ b/pkg/op/storage.go @@ -5,10 +5,9 @@ import ( "errors" "time" - jose "github.com/go-jose/go-jose/v4" - "golang.org/x/text/language" + jose "github.com/go-jose/go-jose/v3" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type AuthStorage interface { @@ -145,12 +144,6 @@ type CanSetUserinfoFromRequest interface { SetUserinfoFromRequest(ctx context.Context, userinfo *oidc.UserInfo, request IDTokenRequest, scopes []string) error } -// CanGetPrivateClaimsFromRequest is an optional additional interface that may be implemented by -// implementors of Storage. It allows setting the jwt token claims based on the request. -type CanGetPrivateClaimsFromRequest interface { - GetPrivateClaimsFromRequest(ctx context.Context, request TokenRequest, restrictedScopes []string) (map[string]any, error) -} - // Storage is a required parameter for NewOpenIDProvider(). In addition to the // embedded interfaces below, if the passed Storage implements ClientCredentialsStorage // then the grant type "client_credentials" will be supported. In that case, the access @@ -171,8 +164,6 @@ type EndSessionRequest struct { ClientID string IDTokenHintClaims *oidc.IDTokenClaims RedirectURI string - LogoutHint string - UILocales []language.Tag } var ErrDuplicateUserCode = errors.New("user code already exists") diff --git a/pkg/op/token.go b/pkg/op/token.go index 2e25d05..63804f2 100644 --- a/pkg/op/token.go +++ b/pkg/op/token.go @@ -2,11 +2,11 @@ package op import ( "context" - "slices" "time" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/crypto" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/strings" ) type TokenCreator interface { @@ -65,7 +65,6 @@ func CreateTokenResponse(ctx context.Context, request IDTokenRequest, client Cli TokenType: oidc.BearerToken, ExpiresIn: exp, State: state, - Scope: request.GetScopes(), }, nil } @@ -83,13 +82,13 @@ func createTokens(ctx context.Context, tokenRequest TokenRequest, storage Storag func needsRefreshToken(tokenRequest TokenRequest, client AccessTokenClient) bool { switch req := tokenRequest.(type) { case AuthRequest: - return slices.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken) + return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && req.GetResponseType() == oidc.ResponseTypeCode && ValidateGrantType(client, oidc.GrantTypeRefreshToken) case TokenExchangeRequest: return req.GetRequestedTokenType() == oidc.RefreshTokenType case RefreshTokenRequest: return true case *DeviceAuthorizationState: - return slices.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && ValidateGrantType(client, oidc.GrantTypeRefreshToken) + return strings.Contains(req.GetScopes(), oidc.ScopeOfflineAccess) && ValidateGrantType(client, oidc.GrantTypeRefreshToken) default: return false } @@ -147,11 +146,7 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex tokenExchangeRequest, ) } else { - if fromRequest, ok := storage.(CanGetPrivateClaimsFromRequest); ok { - privateClaims, err = fromRequest.GetPrivateClaimsFromRequest(ctx, tokenRequest, removeUserinfoScopes(restrictedScopes)) - } else { - privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) - } + privateClaims, err = storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes)) } if err != nil { diff --git a/pkg/op/token_client_credentials.go b/pkg/op/token_client_credentials.go index ddb2fbf..f59aac8 100644 --- a/pkg/op/token_client_credentials.go +++ b/pkg/op/token_client_credentials.go @@ -5,8 +5,8 @@ import ( "net/http" "net/url" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // ClientCredentialsExchange handles the OAuth 2.0 client_credentials grant, including @@ -120,6 +120,5 @@ func CreateClientCredentialsTokenResponse(ctx context.Context, tokenRequest Toke AccessToken: accessToken, TokenType: oidc.BearerToken, ExpiresIn: uint64(validity.Seconds()), - Scope: tokenRequest.GetScopes(), }, nil } diff --git a/pkg/op/token_code.go b/pkg/op/token_code.go index 155aa43..a11d7d7 100644 --- a/pkg/op/token_code.go +++ b/pkg/op/token_code.go @@ -4,8 +4,8 @@ import ( "context" "net/http" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // CodeExchange handles the OAuth 2.0 authorization_code grant, including diff --git a/pkg/op/token_exchange.go b/pkg/op/token_exchange.go index 00af485..93c6fe3 100644 --- a/pkg/op/token_exchange.go +++ b/pkg/op/token_exchange.go @@ -7,8 +7,8 @@ import ( "strings" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type TokenExchangeRequest interface { diff --git a/pkg/op/token_intospection.go b/pkg/op/token_intospection.go index bb6a5a0..94f2b2e 100644 --- a/pkg/op/token_intospection.go +++ b/pkg/op/token_intospection.go @@ -5,8 +5,8 @@ import ( "errors" "net/http" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type Introspector interface { diff --git a/pkg/op/token_jwt_profile.go b/pkg/op/token_jwt_profile.go index defb937..bfd409c 100644 --- a/pkg/op/token_jwt_profile.go +++ b/pkg/op/token_jwt_profile.go @@ -5,8 +5,8 @@ import ( "net/http" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type JWTAuthorizationGrantExchanger interface { @@ -89,7 +89,6 @@ func CreateJWTTokenResponse(ctx context.Context, tokenRequest TokenRequest, crea AccessToken: accessToken, TokenType: oidc.BearerToken, ExpiresIn: uint64(validity.Seconds()), - Scope: tokenRequest.GetScopes(), }, nil } diff --git a/pkg/op/token_refresh.go b/pkg/op/token_refresh.go index a87e883..eaac5aa 100644 --- a/pkg/op/token_refresh.go +++ b/pkg/op/token_refresh.go @@ -4,11 +4,11 @@ import ( "context" "errors" "net/http" - "slices" "time" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/strings" ) type RefreshTokenRequest interface { @@ -85,7 +85,7 @@ func ValidateRefreshTokenScopes(requestedScopes []string, authRequest RefreshTok return nil } for _, scope := range requestedScopes { - if !slices.Contains(authRequest.GetScopes(), scope) { + if !strings.Contains(authRequest.GetScopes(), scope) { return oidc.ErrInvalidScope() } } diff --git a/pkg/op/token_request.go b/pkg/op/token_request.go index 3f5af7a..dcb33ee 100644 --- a/pkg/op/token_request.go +++ b/pkg/op/token_request.go @@ -6,8 +6,8 @@ import ( "net/http" "net/url" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type Exchanger interface { @@ -132,19 +132,11 @@ func AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string, // AuthorizeCodeChallenge authorizes a client by validating the code_verifier against the previously sent // code_challenge of the auth request (PKCE) func AuthorizeCodeChallenge(codeVerifier string, challenge *oidc.CodeChallenge) error { - if challenge == nil { - if codeVerifier != "" { - return oidc.ErrInvalidRequest().WithDescription("code_verifier unexpectedly provided") - } - - return nil - } - if codeVerifier == "" { - return oidc.ErrInvalidRequest().WithDescription("code_verifier required") + return oidc.ErrInvalidRequest().WithDescription("code_challenge required") } if !oidc.VerifyCodeChallenge(challenge, codeVerifier) { - return oidc.ErrInvalidGrant().WithDescription("invalid code_verifier") + return oidc.ErrInvalidGrant().WithDescription("invalid code challenge") } return nil } diff --git a/pkg/op/token_request_test.go b/pkg/op/token_request_test.go deleted file mode 100644 index d226af6..0000000 --- a/pkg/op/token_request_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package op_test - -import ( - "testing" - - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" - "github.com/stretchr/testify/assert" -) - -func TestAuthorizeCodeChallenge(t *testing.T) { - tests := []struct { - name string - codeVerifier string - codeChallenge *oidc.CodeChallenge - want func(t *testing.T, err error) - }{ - { - name: "missing both code_verifier and code_challenge", - codeVerifier: "", - codeChallenge: nil, - want: func(t *testing.T, err error) { - assert.Nil(t, err) - }, - }, - { - name: "valid code_verifier", - codeVerifier: "Hello World!", - codeChallenge: &oidc.CodeChallenge{ - Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", - Method: oidc.CodeChallengeMethodS256, - }, - want: func(t *testing.T, err error) { - assert.Nil(t, err) - }, - }, - { - name: "invalid code_verifier", - codeVerifier: "Hi World!", - codeChallenge: &oidc.CodeChallenge{ - Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", - Method: oidc.CodeChallengeMethodS256, - }, - want: func(t *testing.T, err error) { - assert.ErrorContains(t, err, "invalid code_verifier") - }, - }, - { - name: "code_verifier provided without code_challenge", - codeVerifier: "code_verifier", - codeChallenge: nil, - want: func(t *testing.T, err error) { - assert.ErrorContains(t, err, "code_verifier unexpectedly provided") - }, - }, - { - name: "empty code_verifier", - codeVerifier: "", - codeChallenge: &oidc.CodeChallenge{ - Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", - Method: oidc.CodeChallengeMethodS256, - }, - want: func(t *testing.T, err error) { - assert.ErrorContains(t, err, "code_verifier required") - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := op.AuthorizeCodeChallenge(tt.codeVerifier, tt.codeChallenge) - - tt.want(t, err) - }) - } -} diff --git a/pkg/op/token_revocation.go b/pkg/op/token_revocation.go index 049ee15..035a35c 100644 --- a/pkg/op/token_revocation.go +++ b/pkg/op/token_revocation.go @@ -7,8 +7,8 @@ import ( "net/url" "strings" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type Revoker interface { diff --git a/pkg/op/userinfo.go b/pkg/op/userinfo.go index ff75e72..35f1763 100644 --- a/pkg/op/userinfo.go +++ b/pkg/op/userinfo.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" - httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + httphelper "github.com/zitadel/oidc/v4/pkg/http" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type UserinfoProvider interface { diff --git a/pkg/op/verifier_access_token.go b/pkg/op/verifier_access_token.go index 585ca54..651a726 100644 --- a/pkg/op/verifier_access_token.go +++ b/pkg/op/verifier_access_token.go @@ -3,7 +3,7 @@ package op import ( "context" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type AccessTokenVerifier oidc.Verifier diff --git a/pkg/op/verifier_access_token_example_test.go b/pkg/op/verifier_access_token_example_test.go index b97a7fd..e868c7f 100644 --- a/pkg/op/verifier_access_token_example_test.go +++ b/pkg/op/verifier_access_token_example_test.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) // MyCustomClaims extends the TokenClaims base, diff --git a/pkg/op/verifier_access_token_test.go b/pkg/op/verifier_access_token_test.go index 5845f9f..7627f49 100644 --- a/pkg/op/verifier_access_token_test.go +++ b/pkg/op/verifier_access_token_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func TestNewAccessTokenVerifier(t *testing.T) { diff --git a/pkg/op/verifier_id_token_hint.go b/pkg/op/verifier_id_token_hint.go index 02610aa..542c49e 100644 --- a/pkg/op/verifier_id_token_hint.go +++ b/pkg/op/verifier_id_token_hint.go @@ -4,7 +4,7 @@ import ( "context" "errors" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/oidc" ) type IDTokenHintVerifier oidc.Verifier diff --git a/pkg/op/verifier_id_token_hint_test.go b/pkg/op/verifier_id_token_hint_test.go index 347e33c..4e47a81 100644 --- a/pkg/op/verifier_id_token_hint_test.go +++ b/pkg/op/verifier_id_token_hint_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" ) func TestNewIDTokenHintVerifier(t *testing.T) { diff --git a/pkg/op/verifier_jwt_profile.go b/pkg/op/verifier_jwt_profile.go index 85bfb14..ec7d22c 100644 --- a/pkg/op/verifier_jwt_profile.go +++ b/pkg/op/verifier_jwt_profile.go @@ -6,9 +6,9 @@ import ( "fmt" "time" - jose "github.com/go-jose/go-jose/v4" + jose "github.com/go-jose/go-jose/v3" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/oidc" ) // JWTProfileVerfiier extends oidc.Verifier with diff --git a/pkg/op/verifier_jwt_profile_test.go b/pkg/op/verifier_jwt_profile_test.go index 2068678..adb5236 100644 --- a/pkg/op/verifier_jwt_profile_test.go +++ b/pkg/op/verifier_jwt_profile_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" - tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" - "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tu "github.com/zitadel/oidc/v4/internal/testutil" + "github.com/zitadel/oidc/v4/pkg/oidc" + "github.com/zitadel/oidc/v4/pkg/op" ) func TestNewJWTProfileVerifier(t *testing.T) { diff --git a/pkg/strings/strings.go b/pkg/strings/strings.go index b8f43a1..af48cf3 100644 --- a/pkg/strings/strings.go +++ b/pkg/strings/strings.go @@ -1,9 +1,10 @@ package strings -import "slices" - -// Deprecated: Use standard library [slices.Contains] instead. func Contains(list []string, needle string) bool { - // TODO(v4): remove package. - return slices.Contains(list, needle) + for _, item := range list { + if item == needle { + return true + } + } + return false }