Compare commits

..

12 commits

Author SHA1 Message Date
Tim Möhlmann
214a899ddd Merge branch 'feat-EdDSA-hasher' into webkey-test-branch 2024-08-20 16:07:59 +03:00
Tim Möhlmann
07d4268b89 example: signing algs from discovery 2024-08-20 16:07:20 +03:00
Tim Möhlmann
0df1caff1b Revert "chore(example): add supported signing algorithms to RP"
This reverts commit eb249c4c70.
2024-08-20 16:03:44 +03:00
Tim Möhlmann
6fd74f21d3 Merge branch 'feat-EdDSA-hasher' into work 2024-08-20 11:47:53 +03:00
Tim Möhlmann
248df8c1f1 rp: modify keytype check to support EdDSA 2024-08-20 11:44:09 +03:00
Tim Möhlmann
0e6aafa16c Merge branch 'chore-example-algs' into work 2024-08-20 11:15:33 +03:00
Tim Möhlmann
f5cd665097 Merge branch 'fix-header-map' into work 2024-08-20 10:47:19 +03:00
Tim Möhlmann
2a3e87afff update code comment 2024-08-20 10:44:45 +03:00
Tim Möhlmann
eb249c4c70 chore(example): add supported signing algorithms to RP 2024-08-19 21:04:21 +03:00
Tim Möhlmann
7e1846e6e2 feat(crypto): hash algorithm for EdDSA 2024-08-19 20:57:07 +03:00
Tim Möhlmann
fa73f36780 fix test 2024-08-18 13:14:01 +03:00
Tim Möhlmann
f2545780c8 fix(op): initialize http Headers in response objects 2024-08-17 16:35:30 +03:00
126 changed files with 538 additions and 1550 deletions

View file

@ -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." 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]: " title: "[Bug]: "
labels: ["bug"] labels: ["bug"]
type: Bug
body: body:
- type: markdown - type: markdown
attributes: attributes:

View file

@ -1,7 +1,6 @@
name: 📄 Documentation name: 📄 Documentation
description: Create an issue for missing or wrong documentation. description: Create an issue for missing or wrong documentation.
labels: ["docs"] labels: ["docs"]
type: task
body: body:
- type: markdown - type: markdown
attributes: attributes:

View file

@ -1,12 +1,11 @@
name: 🛠️ Improvement name: 🛠️ Improvement
description: "Create an new issue for an improvment in ZITADEL" description: "Create an new issue for an improvment in ZITADEL"
labels: ["enhancement"] labels: ["improvement"]
type: enhancement
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: | 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 - type: checkboxes
id: preflight id: preflight
attributes: attributes:

44
.github/ISSUE_TEMPLATE/proposal.yaml vendored Normal file
View file

@ -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.

View file

@ -14,11 +14,11 @@ on:
jobs: jobs:
test: test:
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
go: ['1.23', '1.24'] go: ['1.21', '1.22']
name: Go ${{ matrix.go }} test name: Go ${{ matrix.go }} test
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -27,12 +27,12 @@ jobs:
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/... - run: go test -race -v -coverprofile=profile.cov -coverpkg=./pkg/... ./pkg/...
- uses: codecov/codecov-action@v5.4.3 - uses: codecov/codecov-action@v4.5.0
with: with:
file: ./profile.cov file: ./profile.cov
name: codecov-go name: codecov-go
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-20.04
needs: [test] needs: [test]
if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }} if: ${{ github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}
env: env:

101
README.md
View file

@ -21,10 +21,9 @@ Whenever possible we tried to reuse / extend existing packages like `OAuth2 for
## Basic Overview ## Basic Overview
The most important packages of the library: The most important packages of the library:
<pre> <pre>
/pkg /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) /rp definition and implementation of an OIDC Relying Party (client)
/rs definition and implementation of an OAuth Resource Server (API) /rs definition and implementation of an OAuth Resource Server (API)
/op definition and implementation of an OIDC OpenID Provider (server) /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 /server examples of an OpenID Provider implementations (including dynamic) with some very basic login UI
</pre> </pre>
### Semver ### 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. 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.
@ -55,84 +55,48 @@ CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://localhost:9998/ SCOPES="openid
``` ```
- open http://localhost:9999/login in your browser - 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` - login with user `test-user@localhost` and password `verysecure`
- the OP will redirect you to the client app, which displays the user info - the OP will redirect you to the client app, which displays the user info
for the dynamic issuer, just start it with: for the dynamic issuer, just start it with:
```bash ```bash
go run github.com/zitadel/oidc/v3/example/server/dynamic go run github.com/zitadel/oidc/v3/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: 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 ```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/v3/example/client/app
``` ```
> Note: Usernames are suffixed with the hostname (`test-user@localhost` or `test-user@oidc.local`) > 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 ## Features
| | Relying party | OpenID Provider | Specification | | | Relying party | OpenID Provider | Specification |
| -------------------- | ------------- | --------------- | -------------------------------------------- | | -------------------- | ------------- | --------------- | ----------------------------------------- |
| Code Flow | yes | yes | OpenID Connect Core 1.0, [Section 3.1][1] | | 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] | | 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] | | 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] | | Client Credentials | yes | yes | OpenID Connect Core 1.0, [Section 9][4] |
| Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] | | Refresh Token | yes | yes | OpenID Connect Core 1.0, [Section 12][5] |
| Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 | | Discovery | yes | yes | OpenID Connect [Discovery][6] 1.0 |
| JWT Profile | yes | yes | [RFC 7523][7] | | JWT Profile | yes | yes | [RFC 7523][7] |
| PKCE | yes | yes | [RFC 7636][8] | | PKCE | yes | yes | [RFC 7636][8] |
| Token Exchange | yes | yes | [RFC 8693][9] | | Token Exchange | yes | yes | [RFC 8693][9] |
| Device Authorization | yes | yes | [RFC 8628][10] | | Device Authorization | yes | yes | [RFC 8628][10] |
| mTLS | not yet | not yet | [RFC 8705][11] | | mTLS | not yet | not yet | [RFC 8705][11] |
| Back-Channel Logout | not yet | yes | OpenID Connect [Back-Channel Logout][12] 1.0 |
[1]: https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth "3.1. Authentication using the Authorization Code Flow" [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" [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" [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" [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" [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" [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" [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" [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" [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" [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" [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"
## Contributors ## Contributors
@ -151,14 +115,14 @@ For your convenience you can find the relevant guides linked below.
## Supported Go Versions ## 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:. Versions that also build are marked with :warning:.
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| <1.23 | :x: | | <1.21 | :x: |
| 1.23 | :white_check_mark: | | 1.21 | :white_check_mark: |
| 1.24 | :white_check_mark: | | 1.22 | :white_check_mark: |
## Why another library ## 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 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. language governing permissions and limitations under the License.
[^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892 [^1]: https://github.com/zitadel/oidc/issues/135#issuecomment-950563892

View file

@ -13,8 +13,8 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" "github.com/zitadel/oidc/v3/pkg/client/rs"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
const ( const (

View file

@ -14,10 +14,10 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/sirupsen/logrus" "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/logging"
"github.com/zitadel/oidc/v3/pkg/client/rp"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
var ( var (

View file

@ -45,8 +45,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
) )
var ( var (

View file

@ -10,10 +10,10 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
githubOAuth "golang.org/x/oauth2/github" githubOAuth "golang.org/x/oauth2/github"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp/cli" "github.com/zitadel/oidc/v3/pkg/client/rp/cli"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
var ( var (

View file

@ -13,7 +13,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/profile" "github.com/zitadel/oidc/v3/pkg/client/profile"
) )
var client = http.DefaultClient var client = http.DefaultClient

View file

@ -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
}

View file

@ -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)
}
})
}
}

View file

@ -8,7 +8,7 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
const ( const (

View file

@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"golang.org/x/text/language" "golang.org/x/text/language"
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" "github.com/zitadel/oidc/v3/example/server/storage"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
const ( const (

View file

@ -8,10 +8,10 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/zitadel/oidc/v3/pkg/op"
) )
type deviceAuthenticate interface { type deviceAuthenticate interface {

View file

@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/zitadel/oidc/v3/pkg/op"
) )
type login struct { type login struct {

View file

@ -12,13 +12,22 @@ import (
"github.com/zitadel/logging" "github.com/zitadel/logging"
"golang.org/x/text/language" "golang.org/x/text/language"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/example/server/storage"
"github.com/zitadel/oidc/v3/pkg/op"
) )
const ( const (
pathLoggedOut = "/logged-out" pathLoggedOut = "/logged-out"
) )
func init() {
storage.RegisterClients(
storage.NativeClient("native"),
storage.WebClient("web", "secret"),
storage.WebClient("api", "secret"),
)
}
type Storage interface { type Storage interface {
op.Storage op.Storage
authenticate authenticate

View file

@ -6,53 +6,36 @@ import (
"net/http" "net/http"
"os" "os"
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/config" "github.com/zitadel/oidc/v3/example/server/exampleop"
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop" "github.com/zitadel/oidc/v3/example/server/storage"
"git.christmann.info/LARA/zitadel-oidc/v3/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() { 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( logger := slog.New(
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
AddSource: true, AddSource: true,
Level: slog.LevelDebug, 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) router := exampleop.SetupServer(issuer, storage, logger, false)
server := &http.Server{ server := &http.Server{
Addr: ":" + cfg.Port, Addr: ":" + port,
Handler: router, Handler: router,
} }
logger.Info("server listening, press ctrl+c to stop", "addr", issuer) logger.Info("server listening, press ctrl+c to stop", "addr", fmt.Sprintf("http://localhost:%s/", port))
if server.ListenAndServe() != http.ErrServerClosed { err := server.ListenAndServe()
if err != http.ErrServerClosed {
logger.Error("server terminated", "error", err) logger.Error("server terminated", "error", err)
os.Exit(1) os.Exit(1)
} }

View file

@ -3,8 +3,8 @@ package storage
import ( import (
"time" "time"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
var ( var (

View file

@ -6,8 +6,8 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
const ( const (
@ -121,7 +121,7 @@ func (a *AuthRequest) Done() bool {
} }
func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string { func PromptToInternal(oidcPrompt oidc.SpaceDelimitedArray) []string {
prompts := make([]string, 0, len(oidcPrompt)) prompts := make([]string, len(oidcPrompt))
for _, oidcPrompt := range oidcPrompt { for _, oidcPrompt := range oidcPrompt {
switch oidcPrompt { switch oidcPrompt {
case oidc.PromptNone, 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 { type OIDCCodeChallenge struct {
Challenge string Challenge string
Method string Method string

View file

@ -14,8 +14,8 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
"github.com/google/uuid" "github.com/google/uuid"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant // 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 // 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 // therefore we will also just check this boolean if the request / login has been finished
request.done = true request.done = true
request.authTime = time.Now()
return nil return nil
} }
return fmt.Errorf("username or password wrong") 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 // 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 // we therefore will have to check the currentRefreshToken and renew the refresh token
refreshToken, refreshTokenID, err := s.renewRefreshToken(currentRefreshToken)
newRefreshToken = uuid.NewString()
accessToken, err := s.accessToken(applicationID, newRefreshToken, request.GetSubject(), request.GetAudience(), request.GetScopes())
if err != nil { if err != nil {
return "", "", time.Time{}, err return "", "", time.Time{}, err
} }
accessToken, err := s.accessToken(applicationID, refreshTokenID, request.GetSubject(), request.GetAudience(), request.GetScopes())
if err := s.renewRefreshToken(currentRefreshToken, newRefreshToken, accessToken.ID); err != nil { if err != nil {
return "", "", time.Time{}, err return "", "", time.Time{}, err
} }
return accessToken.ID, refreshToken, accessToken.Expiration, nil
return accessToken.ID, newRefreshToken, accessToken.Expiration, nil
} }
func (s *Storage) exchangeRefreshToken(ctx context.Context, request op.TokenExchangeRequest) (accessTokenID string, newRefreshToken string, expiration time.Time, err error) { 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 { if refreshToken.ApplicationID != clientID {
return oidc.ErrInvalidClient().WithDescription("token was not issued for this client") 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 // 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 return nil
} }
@ -490,9 +488,6 @@ func (s *Storage) SetUserinfoFromToken(ctx context.Context, userinfo *oidc.UserI
// return err // 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) 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, Audience: accessToken.Audience,
Expiration: time.Now().Add(5 * time.Hour), Expiration: time.Now().Add(5 * time.Hour),
Scopes: accessToken.Scopes, Scopes: accessToken.Scopes,
AccessToken: accessToken.ID,
} }
s.refreshTokens[token.ID] = token s.refreshTokens[token.ID] = token
return token.Token, nil return token.Token, nil
} }
// renewRefreshToken checks the provided refresh_token and creates a new one based on the current // renewRefreshToken checks the provided refresh_token and creates a new one based on the current
// func (s *Storage) renewRefreshToken(currentRefreshToken string) (string, string, error) {
// [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 {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
refreshToken, ok := s.refreshTokens[currentRefreshToken] refreshToken, ok := s.refreshTokens[currentRefreshToken]
if !ok { 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(s.refreshTokens, currentRefreshToken)
for _, token := range s.tokens {
// delete the access token which was issued based on this refresh token if token.RefreshTokenID == currentRefreshToken {
delete(s.tokens, refreshToken.AccessToken) delete(s.tokens, token.ID)
break
if refreshToken.Expiration.Before(time.Now()) { }
return fmt.Errorf("expired refresh token")
} }
// creates a new refresh token based on the current one // creates a new refresh token based on the current one
refreshToken.Token = newRefreshToken token := uuid.NewString()
refreshToken.ID = newRefreshToken refreshToken.Token = token
refreshToken.Expiration = time.Now().Add(5 * time.Hour) refreshToken.ID = token
refreshToken.AccessToken = newAccessToken s.refreshTokens[token] = refreshToken
s.refreshTokens[newRefreshToken] = refreshToken return token, refreshToken.ID, nil
return nil
} }
// accessToken will store an access_token in-memory based on the provided information // accessToken will store an access_token in-memory based on the provided information

View file

@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
type multiStorage struct { type multiStorage struct {

View file

@ -22,5 +22,4 @@ type RefreshToken struct {
ApplicationID string ApplicationID string
Expiration time.Time Expiration time.Time
Scopes []string Scopes []string
AccessToken string // Token.ID
} }

View file

@ -2,8 +2,6 @@ package storage
import ( import (
"crypto/rsa" "crypto/rsa"
"encoding/json"
"os"
"strings" "strings"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -37,18 +35,6 @@ type userStore struct {
users map[string]*User 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 { func NewUserStore(issuer string) UserStore {
hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0] hostname := strings.Split(strings.Split(issuer, "://")[1], ":")[0]
return userStore{ return userStore{

View file

@ -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)
}
})
}
}

38
go.mod
View file

@ -1,28 +1,26 @@
module git.christmann.info/LARA/zitadel-oidc/v3 module github.com/zitadel/oidc/v3
go 1.23.7 go 1.21
toolchain go1.24.1
require ( require (
github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.1.0
github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-jose/go-jose/v4 v4.0.4
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/go-github/v31 v31.0.0 github.com/google/go-github/v31 v31.0.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/securecookie v1.1.2 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/gu v0.3.1
github.com/muhlemmer/httpforwarded v0.1.0 github.com/muhlemmer/httpforwarded v0.1.0
github.com/rs/cors v1.11.1 github.com/rs/cors v1.11.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
github.com/zitadel/logging v0.6.2 github.com/zitadel/logging v0.6.0
github.com/zitadel/schema v1.3.1 github.com/zitadel/schema v1.3.0
go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel v1.28.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.22.0
golang.org/x/text v0.26.0 golang.org/x/text v0.17.0
) )
require ( require (
@ -31,10 +29,10 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/metric v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.28.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

68
go.sum
View file

@ -1,12 +1,12 @@
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 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 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -29,8 +29,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/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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 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.7 h1:At0OhGCFGPXyjPYAsCchoBUhE099pcBXmsb4iZqROIc=
github.com/jeremija/gosubmit v0.2.8/go.mod h1:Ui+HS073lCFREXBbdfrJzMB57OI/bdxTiLtrDHHhFPI= 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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
@ -41,40 +41,40 @@ github.com/muhlemmer/httpforwarded v0.1.0 h1:x4DLrzXdliq8mprgUMR0olDvHGkou5BJsK/
github.com/muhlemmer/httpforwarded v0.1.0/go.mod h1:yo9czKedo2pdZhoXe+yDkGVbU0TJ0q9oQ90BVoDEtw0= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/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.0 h1:t5Nnt//r+m2ZhhoTmoPX+c96pbMarqJvW1Vq6xFTank=
github.com/zitadel/logging v0.6.2/go.mod h1:z6VWLWUkJpnNVDSLzrPSQSQyttysKZ6bCRongw0ROK4= github.com/zitadel/logging v0.6.0/go.mod h1:Y4CyAXHpl3Mig6JOszcV5Rqqsojj+3n7y2F591Mp/ow=
github.com/zitadel/schema v1.3.1 h1:QT3kwiRIRXXLVAs6gCK/u044WmUVh6IlbLXUsn6yRQU= github.com/zitadel/schema v1.3.0 h1:kQ9W9tvIwZICCKWcMvCEweXET1OcOyGEuFbHs4o5kg0=
github.com/zitadel/schema v1.3.1/go.mod h1:071u7D2LQacy1HAN+YnMd/mx1qVE2isb0Mjeqg46xnU= github.com/zitadel/schema v1.3.0/go.mod h1:NptN6mkBDFvERUCvZHlvWmmME+gmZ44xzwRXwhzsbtc=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-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-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20210220032951-036812b2e83c/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -83,13 +83,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.22.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@ -101,8 +101,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -8,8 +8,8 @@ import (
"fmt" "fmt"
"os" "os"
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" tu "github.com/zitadel/oidc/v3/internal/testutil"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
var custom = map[string]any{ var custom = map[string]any{

View file

@ -8,9 +8,9 @@ import (
"errors" "errors"
"time" "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/v4"
"github.com/muhlemmer/gu" "github.com/muhlemmer/gu"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
// KeySet implements oidc.Keys // KeySet implements oidc.Keys

View file

@ -15,9 +15,9 @@ import (
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" "github.com/zitadel/oidc/v3/pkg/crypto"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
var ( var (
@ -42,7 +42,7 @@ func Discover(ctx context.Context, issuer string, httpClient *http.Client, wellK
discoveryConfig := new(oidc.DiscoveryConfiguration) discoveryConfig := new(oidc.DiscoveryConfiguration)
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig) err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
if err != nil { if err != nil {
return nil, errors.Join(oidc.ErrDiscoveryFailed, err) return nil, err
} }
if logger, ok := logging.FromContext(ctx); ok { if logger, ok := logging.FromContext(ctx); ok {
logger.Debug("discover", "config", discoveryConfig) logger.Debug("discover", "config", discoveryConfig)

View file

@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"testing" "testing"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -23,7 +22,7 @@ func TestDiscover(t *testing.T) {
name string name string
args args args args
wantFields *wantFields wantFields *wantFields
wantErr error wantErr bool
}{ }{
{ {
name: "spotify", // https://github.com/zitadel/oidc/issues/406 name: "spotify", // https://github.com/zitadel/oidc/issues/406
@ -33,20 +32,17 @@ func TestDiscover(t *testing.T) {
wantFields: &wantFields{ wantFields: &wantFields{
UILocalesSupported: true, UILocalesSupported: true,
}, },
wantErr: nil, wantErr: false,
},
{
name: "discovery failed",
args: args{
issuer: "https://example.com",
},
wantErr: oidc.ErrDiscoveryFailed,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := Discover(context.Background(), tt.args.issuer, http.DefaultClient, tt.args.wellKnownUrl...) 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 { if tt.wantFields == nil {
return return
} }

View file

@ -23,14 +23,14 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/exampleop" "github.com/zitadel/oidc/v3/example/server/exampleop"
"git.christmann.info/LARA/zitadel-oidc/v3/example/server/storage" "github.com/zitadel/oidc/v3/example/server/storage"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" "github.com/zitadel/oidc/v3/pkg/client/rs"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/tokenexchange" "github.com/zitadel/oidc/v3/pkg/client/tokenexchange"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
var Logger = slog.New( var Logger = slog.New(

View file

@ -6,8 +6,8 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
// JWTProfileExchange handles the oauth2 jwt profile exchange // JWTProfileExchange handles the oauth2 jwt profile exchange

View file

@ -2,7 +2,7 @@ package client
import ( import (
"encoding/json" "encoding/json"
"os" "io/ioutil"
) )
const ( const (
@ -24,7 +24,7 @@ type KeyFile struct {
} }
func ConfigFromKeyFile(path string) (*KeyFile, error) { func ConfigFromKeyFile(path string) (*KeyFile, error) {
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -8,8 +8,8 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "github.com/zitadel/oidc/v3/pkg/client"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type TokenSource interface { type TokenSource interface {

View file

@ -4,9 +4,9 @@ import (
"context" "context"
"net/http" "net/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
const ( const (

View file

@ -1,7 +1,7 @@
package rp package rp
import ( import (
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange" "github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
) )
// DelegationTokenRequest is an implementation of TokenExchangeRequest // DelegationTokenRequest is an implementation of TokenExchangeRequest

View file

@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"time" "time"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "github.com/zitadel/oidc/v3/pkg/client"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) { func newDeviceClientCredentialsRequest(scopes []string, rp RelyingParty) (*oidc.ClientCredentialsRequest, error) {

View file

@ -9,9 +9,9 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "github.com/zitadel/oidc/v3/pkg/client"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet { func NewRemoteKeySet(client *http.Client, jwksURL string, opts ...func(*remoteKeySet)) oidc.KeySet {

View file

@ -14,10 +14,10 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials" "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/logging"
"github.com/zitadel/oidc/v3/pkg/client"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
const ( const (
@ -541,7 +541,7 @@ func CodeExchangeHandler[C oidc.IDClaims](callback CodeExchangeCallback[C], rp R
rp.CookieHandler().DeleteCookie(w, pkceCode) rp.CookieHandler().DeleteCookie(w, pkceCode)
} }
if rp.Signer() != nil { 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 { if err != nil {
unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp) unauthorizedError(w, r, "failed to build assertion: "+err.Error(), state, rp)
return return

View file

@ -5,10 +5,10 @@ import (
"testing" "testing"
"time" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
tu "github.com/zitadel/oidc/v3/internal/testutil"
"github.com/zitadel/oidc/v3/pkg/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )

View file

@ -5,7 +5,7 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc/grants/tokenexchange" "github.com/zitadel/oidc/v3/pkg/oidc/grants/tokenexchange"
) )
// TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange` // TokenExchangeRP extends the `RelyingParty` interface for the *draft* oauth2 `Token Exchange`

View file

@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type UserInfo struct { type UserInfo struct {

View file

@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "github.com/zitadel/oidc/v3/pkg/client"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
// VerifyTokens implement the Token Response Validation as defined in OIDC specification // VerifyTokens implement the Token Response Validation as defined in OIDC specification

View file

@ -5,11 +5,11 @@ import (
"testing" "testing"
"time" "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/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
tu "github.com/zitadel/oidc/v3/internal/testutil"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
func TestVerifyTokens(t *testing.T) { func TestVerifyTokens(t *testing.T) {

View file

@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
tu "git.christmann.info/LARA/zitadel-oidc/v3/internal/testutil" tu "github.com/zitadel/oidc/v3/internal/testutil"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/client/rp"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
// MyCustomClaims extends the TokenClaims base, // MyCustomClaims extends the TokenClaims base,

View file

@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client/rs" "github.com/zitadel/oidc/v3/pkg/client/rs"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type IntrospectionResponse struct { type IntrospectionResponse struct {

View file

@ -6,9 +6,9 @@ import (
"net/http" "net/http"
"time" "time"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client" "github.com/zitadel/oidc/v3/pkg/client"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type ResourceServer interface { type ResourceServer interface {

View file

@ -4,9 +4,9 @@ import (
"context" "context"
"testing" "testing"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
func TestNewResourceServer(t *testing.T) { func TestNewResourceServer(t *testing.T) {

View file

@ -6,10 +6,10 @@ import (
"net/http" "net/http"
"time" "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/v4"
"github.com/zitadel/oidc/v3/pkg/client"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
type TokenExchanger interface { type TokenExchanger interface {

View file

@ -10,7 +10,7 @@ import (
"github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
zcrypto "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" zcrypto "github.com/zitadel/oidc/v3/pkg/crypto"
) )
func TestBytesToPrivateKey(t *testing.T) { func TestBytesToPrivateKey(t *testing.T) {

View file

@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
var DefaultHTTPClient = &http.Client{ var DefaultHTTPClient = &http.Client{

View file

@ -3,7 +3,7 @@ package oidc
import ( import (
"crypto/sha256" "crypto/sha256"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" "github.com/zitadel/oidc/v3/pkg/crypto"
) )
const ( const (

View file

@ -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 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"` 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 type AuthMethod string

View file

@ -133,7 +133,6 @@ type Error struct {
ErrorType errorType `json:"error" schema:"error"` ErrorType errorType `json:"error" schema:"error"`
Description string `json:"error_description,omitempty" schema:"error_description,omitempty"` Description string `json:"error_description,omitempty" schema:"error_description,omitempty"`
State string `json:"state,omitempty" schema:"state,omitempty"` State string `json:"state,omitempty" schema:"state,omitempty"`
SessionState string `json:"session_state,omitempty" schema:"session_state,omitempty"`
redirectDisabled bool `schema:"-"` redirectDisabled bool `schema:"-"`
returnParent bool `schema:"-"` returnParent bool `schema:"-"`
} }
@ -143,13 +142,11 @@ func (e *Error) MarshalJSON() ([]byte, error) {
Error errorType `json:"error"` Error errorType `json:"error"`
ErrorDescription string `json:"error_description,omitempty"` ErrorDescription string `json:"error_description,omitempty"`
State string `json:"state,omitempty"` State string `json:"state,omitempty"`
SessionState string `json:"session_state,omitempty"`
Parent string `json:"parent,omitempty"` Parent string `json:"parent,omitempty"`
}{ }{
Error: e.ErrorType, Error: e.ErrorType,
ErrorDescription: e.Description, ErrorDescription: e.Description,
State: e.State, State: e.State,
SessionState: e.SessionState,
} }
if e.returnParent { if e.returnParent {
m.Parent = e.Parent.Error() m.Parent = e.Parent.Error()
@ -179,8 +176,7 @@ func (e *Error) Is(target error) bool {
} }
return e.ErrorType == t.ErrorType && return e.ErrorType == t.ErrorType &&
(e.Description == t.Description || t.Description == "") && (e.Description == t.Description || t.Description == "") &&
(e.State == t.State || t.State == "") && (e.State == t.State || t.State == "")
(e.SessionState == t.SessionState || t.SessionState == "")
} }
func (e *Error) WithParent(err error) *Error { func (e *Error) WithParent(err error) *Error {
@ -246,9 +242,6 @@ func (e *Error) LogValue() slog.Value {
if e.State != "" { if e.State != "" {
attrs = append(attrs, slog.String("state", 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 { if e.redirectDisabled {
attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled)) attrs = append(attrs, slog.Bool("redirect_disabled", e.redirectDisabled))
} }

View file

@ -1,12 +1,10 @@
package oidc package oidc
// EndSessionRequest for the RP-Initiated Logout according to: // 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 { type EndSessionRequest struct {
IdTokenHint string `schema:"id_token_hint"` IdTokenHint string `schema:"id_token_hint"`
LogoutHint string `schema:"logout_hint"` ClientID string `schema:"client_id"`
ClientID string `schema:"client_id"` PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"` State string `schema:"state"`
State string `schema:"state"`
UILocales Locales `schema:"ui_locales"`
} }

View file

@ -10,7 +10,7 @@ import (
"github.com/muhlemmer/gu" "github.com/muhlemmer/gu"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" "github.com/zitadel/oidc/v3/pkg/crypto"
) )
const ( const (
@ -117,7 +117,6 @@ func NewAccessTokenClaims(issuer, subject string, audience []string, expiration
Expiration: FromTime(expiration), Expiration: FromTime(expiration),
IssuedAt: FromTime(now), IssuedAt: FromTime(now),
NotBefore: FromTime(now), NotBefore: FromTime(now),
ClientID: clientID,
JWTID: jwtid, JWTID: jwtid,
}, },
} }
@ -230,13 +229,12 @@ func (c *ActorClaims) UnmarshalJSON(data []byte) error {
} }
type AccessTokenResponse struct { type AccessTokenResponse struct {
AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"` AccessToken string `json:"access_token,omitempty" schema:"access_token,omitempty"`
TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"` TokenType string `json:"token_type,omitempty" schema:"token_type,omitempty"`
RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"` RefreshToken string `json:"refresh_token,omitempty" schema:"refresh_token,omitempty"`
ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"` ExpiresIn uint64 `json:"expires_in,omitempty" schema:"expires_in,omitempty"`
IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"` IDToken string `json:"id_token,omitempty" schema:"id_token,omitempty"`
State string `json:"state,omitempty" schema:"state,omitempty"` State string `json:"state,omitempty" schema:"state,omitempty"`
Scope SpaceDelimitedArray `json:"scope,omitempty" schema:"scope,omitempty"`
} }
type JWTProfileAssertionClaims struct { type JWTProfileAssertionClaims struct {
@ -383,40 +381,3 @@ type TokenExchangeResponse struct {
// if the requested_token_type was Access Token and scope contained openid. // if the requested_token_type was Access Token and scope contained openid.
IDToken string `json:"id_token,omitempty"` 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,
}
}

View file

@ -72,10 +72,10 @@ type AccessTokenRequest struct {
Code string `schema:"code"` Code string `schema:"code"`
RedirectURI string `schema:"redirect_uri"` RedirectURI string `schema:"redirect_uri"`
ClientID string `schema:"client_id"` ClientID string `schema:"client_id"`
ClientSecret string `schema:"client_secret,omitempty"` ClientSecret string `schema:"client_secret"`
CodeVerifier string `schema:"code_verifier,omitempty"` CodeVerifier string `schema:"code_verifier"`
ClientAssertion string `schema:"client_assertion,omitempty"` ClientAssertion string `schema:"client_assertion"`
ClientAssertionType string `schema:"client_assertion_type,omitempty"` ClientAssertionType string `schema:"client_assertion_type"`
} }
func (a *AccessTokenRequest) GrantType() GrantType { func (a *AccessTokenRequest) GrantType() GrantType {

View file

@ -145,7 +145,6 @@ func TestNewAccessTokenClaims(t *testing.T) {
Subject: "hello@me.com", Subject: "hello@me.com",
Audience: Audience{"foo"}, Audience: Audience{"foo"},
Expiration: 12345, Expiration: 12345,
ClientID: "foo",
JWTID: "900", JWTID: "900",
}, },
} }
@ -242,39 +241,3 @@ func TestIDTokenClaims_GetUserInfo(t *testing.T) {
got := idTokenData.GetUserInfo() got := idTokenData.GetUserInfo()
assert.Equal(t, want, got) 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)
}

View file

@ -35,17 +35,6 @@ func (a *Audience) UnmarshalJSON(text []byte) error {
return nil 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 type Display string
func (d *Display) UnmarshalText(text []byte) error { 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. // to an empty value (language "und") and no error will be returned.
// This state can be checked with the `l.Tag().IsRoot()` method. // This state can be checked with the `l.Tag().IsRoot()` method.
func (l *Locale) UnmarshalJSON(data []byte) error { func (l *Locale) UnmarshalJSON(data []byte) error {
if len(data) == 0 || string(data) == "\"\"" {
return nil
}
err := json.Unmarshal(data, &l.tag) err := json.Unmarshal(data, &l.tag)
if err == nil { if err == nil {
return nil return nil
@ -126,14 +112,6 @@ func ParseLocales(locales []string) Locales {
return out 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. // UnmarshalText implements the [encoding.TextUnmarshaler] interface.
// It decodes an unquoted space seperated string into Locales. // It decodes an unquoted space seperated string into Locales.
// Undefined language tags in the input are ignored and ommited from // 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 { e.RegisterEncoder(SpaceDelimitedArray{}, func(value reflect.Value) string {
return value.Interface().(SpaceDelimitedArray).String() return value.Interface().(SpaceDelimitedArray).String()
}) })
e.RegisterEncoder(Locales{}, func(value reflect.Value) string {
return value.Interface().(Locales).String()
})
return e return e
} }

View file

@ -217,30 +217,6 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
want dst want dst
wantErr bool 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", name: "afrikaans, ok",
input: `{"locale": "af"}`, input: `{"locale": "af"}`,
@ -261,17 +237,16 @@ func TestLocale_UnmarshalJSON(t *testing.T) {
wantErr: true, wantErr: true,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { var got dst
var got dst err := json.Unmarshal([]byte(tt.input), &got)
err := json.Unmarshal([]byte(tt.input), &got) if tt.wantErr {
if tt.wantErr { require.Error(t, err)
require.Error(t, err) return
return }
} require.NoError(t, err)
require.NoError(t, err) assert.Equal(t, tt.want, got)
assert.Equal(t, tt.want, got)
})
} }
} }

View file

@ -7,11 +7,12 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"slices"
"strings" "strings"
"time" "time"
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
str "github.com/zitadel/oidc/v3/pkg/strings"
) )
type Claims interface { type Claims interface {
@ -40,7 +41,6 @@ type IDClaims interface {
var ( var (
ErrParse = errors.New("parsing of request failed") ErrParse = errors.New("parsing of request failed")
ErrIssuerInvalid = errors.New("issuer does not match") ErrIssuerInvalid = errors.New("issuer does not match")
ErrDiscoveryFailed = errors.New("OpenID Provider Configuration Discovery has failed")
ErrSubjectMissing = errors.New("subject missing") ErrSubjectMissing = errors.New("subject missing")
ErrAudience = errors.New("audience is not valid") 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") 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 // if none of the provided values matches the acr claim
func DefaultACRVerifier(possibleValues []string) ACRVerifier { func DefaultACRVerifier(possibleValues []string) ACRVerifier {
return func(acr string) error { 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 fmt.Errorf("expected one of: %v, got: %q", possibleValues, acr)
} }
return nil return nil
@ -122,7 +122,7 @@ func CheckIssuer(claims Claims, issuer string) error {
} }
func CheckAudience(claims Claims, clientID 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) return fmt.Errorf("%w: Audience must contain client_id %q", ErrAudience, clientID)
} }

View file

@ -5,10 +5,10 @@ import (
"encoding/json" "encoding/json"
"testing" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
tu "github.com/zitadel/oidc/v3/internal/testutil"
"github.com/zitadel/oidc/v3/pkg/oidc"
) )
func TestParseToken(t *testing.T) { func TestParseToken(t *testing.T) {

View file

@ -15,9 +15,10 @@ import (
"strings" "strings"
"time" "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" "github.com/bmatcuk/doublestar/v4"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
str "github.com/zitadel/oidc/v3/pkg/strings"
) )
type AuthRequest interface { type AuthRequest interface {
@ -38,13 +39,6 @@ type AuthRequest interface {
Done() bool 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 { type Authorizer interface {
Storage() Storage Storage() Storage
Decoder() httphelper.Decoder Decoder() httphelper.Decoder
@ -62,12 +56,6 @@ type AuthorizeValidator interface {
ValidateAuthRequest(context.Context, *oidc.AuthRequest, Storage, *IDTokenHintVerifier) (string, error) 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) { func authorizeHandler(authorizer Authorizer) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
Authorize(w, r, authorizer) Authorize(w, r, authorizer)
@ -95,29 +83,21 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
if authReq.RequestParam != "" && authorizer.RequestObjectSupported() { if authReq.RequestParam != "" && authorizer.RequestObjectSupported() {
err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx)) err = ParseRequestObject(ctx, authReq, authorizer.Storage(), IssuerFromContext(ctx))
if err != nil { if err != nil {
AuthRequestError(w, r, nil, err, authorizer) AuthRequestError(w, r, authReq, err, authorizer)
return return
} }
} }
if authReq.ClientID == "" { 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 return
} }
if authReq.RedirectURI == "" { 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 return
} }
validation := ValidateAuthRequest
var client Client if validater, ok := authorizer.(AuthorizeValidator); ok {
validation := func(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) { validation = validater.ValidateAuthRequest
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
} }
userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx)) userID, err := validation(ctx, authReq, authorizer.Storage(), authorizer.IDTokenHintVerifier(ctx))
if err != nil { if err != nil {
@ -133,6 +113,11 @@ func Authorize(w http.ResponseWriter, r *http.Request, authorizer Authorizer) {
AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer) AuthRequestError(w, r, authReq, oidc.DefaultToServerError(err, "unable to save auth request"), authorizer)
return 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) RedirectToLogin(req.GetID(), client, w, r)
} }
@ -168,7 +153,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
if requestObject.Issuer != requestObject.ClientID { if requestObject.Issuer != requestObject.ClientID {
return oidc.ErrInvalidRequest().WithDescription("missing or wrong issuer in request") 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") return oidc.ErrInvalidRequest().WithDescription("issuer missing in audience")
} }
keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer} keySet := &jwtProfileKeySet{storage: storage, clientID: requestObject.Issuer}
@ -182,7 +167,7 @@ func ParseRequestObject(ctx context.Context, authReq *oidc.AuthRequest, storage
// CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request // CopyRequestObjectToAuthRequest overwrites present values from the Request Object into the auth request
// and clears the `RequestParam` of the auth request // and clears the `RequestParam` of the auth request
func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oidc.RequestObject) { 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 authReq.Scopes = requestObject.Scopes
} }
if requestObject.RedirectURI != "" { if requestObject.RedirectURI != "" {
@ -227,37 +212,26 @@ func CopyRequestObjectToAuthRequest(authReq *oidc.AuthRequest, requestObject *oi
authReq.RequestParam = "" authReq.RequestParam = ""
} }
// ValidateAuthRequest validates the authorize parameters and returns the userID of the id_token_hint if passed. // 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.
func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) { func ValidateAuthRequest(ctx context.Context, authReq *oidc.AuthRequest, storage Storage, verifier *IDTokenHintVerifier) (sub string, err error) {
ctx, span := tracer.Start(ctx, "ValidateAuthRequest") ctx, span := tracer.Start(ctx, "ValidateAuthRequest")
defer span.End() 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) authReq.MaxAge, err = ValidateAuthReqPrompt(authReq.Prompt, authReq.MaxAge)
if err != nil { if err != nil {
return "", err 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) authReq.Scopes, err = ValidateAuthReqScopes(client, authReq.Scopes)
if err != nil { if err != nil {
return "", err return "", err
} }
if err := ValidateAuthReqRedirectURI(client, authReq.RedirectURI, authReq.ResponseType); err != nil {
return "", err
}
if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil { if err := ValidateAuthReqResponseType(client, authReq.ResponseType); err != nil {
return "", err return "", err
} }
@ -300,7 +274,7 @@ func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
// checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores // checkURIAgainstRedirects just checks aginst the valid redirect URIs and ignores
// other factors. // other factors.
func checkURIAgainstRedirects(client Client, uri string) error { func checkURIAgainstRedirects(client Client, uri string) error {
if slices.Contains(client.RedirectURIs(), uri) { if str.Contains(client.RedirectURIs(), uri) {
return nil return nil
} }
if globClient, ok := client.(HasRedirectGlobs); ok { if globClient, ok := client.(HasRedirectGlobs); ok {
@ -325,12 +299,12 @@ func ValidateAuthReqRedirectURI(client Client, uri string, responseType oidc.Res
return oidc.ErrInvalidRequestRedirectURI().WithDescription("The redirect_uri is missing in the request. " + 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.") "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://") { if strings.HasPrefix(uri, "https://") {
return checkURIAgainstRedirects(client, uri) return checkURIAgainstRedirects(client, uri)
} }
if client.ApplicationType() == ApplicationTypeNative {
return validateAuthReqRedirectURINative(client, uri)
}
if err := checkURIAgainstRedirects(client, uri); err != nil { if err := checkURIAgainstRedirects(client, uri); err != nil {
return err return err
} }
@ -351,15 +325,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 // ValidateAuthReqRedirectURINative validates the passed redirect_uri and response_type to the registered uris and client type
func validateAuthReqRedirectURINative(client Client, uri string) error { func validateAuthReqRedirectURINative(client Client, uri string) error {
parsedURL, isLoopback := HTTPLoopbackOrLocalhost(uri) 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 err := checkURIAgainstRedirects(client, uri); err == nil {
if client.DevMode() { if client.DevMode() {
return nil return nil
} }
if !isLoopback && strings.HasPrefix(uri, "https://") { // The RedirectURIs are only valid for native clients when localhost or non-"http://"
return nil
}
// The RedirectURIs are only valid for native clients when localhost or non-"http://" and "https://"
if isLoopback || isCustomSchema { if isLoopback || isCustomSchema {
return nil return nil
} }
@ -389,11 +360,11 @@ func HTTPLoopbackOrLocalhost(rawURL string) (*url.URL, bool) {
if err != nil { if err != nil {
return nil, false return nil, false
} }
if parsedURL.Scheme == "http" || parsedURL.Scheme == "https" { if parsedURL.Scheme != "http" {
hostName := parsedURL.Hostname() return nil, false
return parsedURL, hostName == "localhost" || net.ParseIP(hostName).IsLoopback()
} }
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 // ValidateAuthReqResponseType validates the passed response_type to the registered response types
@ -483,70 +454,41 @@ func AuthResponse(authReq AuthRequest, authorizer Authorizer, w http.ResponseWri
AuthResponseToken(w, r, authReq, authorizer, client) 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) { func AuthResponseCode(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) {
ctx, span := tracer.Start(r.Context(), "AuthResponseCode") ctx, span := tracer.Start(r.Context(), "AuthResponseCode")
defer span.End()
r = r.WithContext(ctx) r = r.WithContext(ctx)
defer span.End()
var err error code, err := CreateAuthRequestCode(r.Context(), authReq, authorizer.Storage(), authorizer.Crypto())
if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
err = handleFormPostResponse(w, r, authReq, authorizer)
} else {
err = handleRedirectResponse(w, r, authReq, authorizer)
}
if err != nil { if err != nil {
AuthRequestError(w, r, authReq, err, authorizer) 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 if authReq.GetResponseMode() == oidc.ResponseModeFormPost {
func handleFormPostResponse(w http.ResponseWriter, r *http.Request, authReq AuthRequest, authorizer Authorizer) error { err := AuthResponseFormPost(w, authReq.GetRedirectURI(), &codeResponse, authorizer.Encoder())
codeResponse, err := BuildAuthResponseCodeResponsePayload(r.Context(), authReq, authorizer) 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 { if err != nil {
return err AuthRequestError(w, r, authReq, err, authorizer)
return
} }
return AuthResponseFormPost(w, authReq.GetRedirectURI(), codeResponse, authorizer.Encoder()) http.Redirect(w, r, callback, http.StatusFound)
}
// 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())
} }
// AuthResponseToken creates the successful token(s) authentication response // AuthResponseToken creates the successful token(s) authentication response

View file

@ -11,15 +11,15 @@ import (
"reflect" "reflect"
"testing" "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/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/example/server/storage"
tu "github.com/zitadel/oidc/v3/internal/testutil"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/oidc/v3/pkg/op/mock"
"github.com/zitadel/schema" "github.com/zitadel/schema"
) )
@ -433,24 +433,6 @@ func TestValidateAuthReqRedirectURI(t *testing.T) {
}, },
false, 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", "code flow unregistered http native fails",
args{ args{
@ -1090,34 +1072,6 @@ func TestAuthResponseCode(t *testing.T) {
wantBody: "", 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 name: "success without state", // reproduce issue #415
args: args{ args: args{
@ -1225,133 +1179,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) { func TestValidateAuthReqIDTokenHint(t *testing.T) {
token, _ := tu.ValidIDToken() token, _ := tu.ValidIDToken()
tests := []struct { tests := []struct {
@ -1382,231 +1209,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)
}
}
})
}
}

View file

@ -7,8 +7,8 @@ import (
"net/url" "net/url"
"time" "time"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
//go:generate go get github.com/dmarkham/enumer //go:generate go get github.com/dmarkham/enumer

View file

@ -10,13 +10,13 @@ import (
"strings" "strings"
"testing" "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/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
httphelper "github.com/zitadel/oidc/v3/pkg/http"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/oidc/v3/pkg/op/mock"
"github.com/zitadel/schema" "github.com/zitadel/schema"
) )

View file

@ -30,7 +30,6 @@ type Configuration interface {
EndSessionEndpoint() *Endpoint EndSessionEndpoint() *Endpoint
KeysEndpoint() *Endpoint KeysEndpoint() *Endpoint
DeviceAuthorizationEndpoint() *Endpoint DeviceAuthorizationEndpoint() *Endpoint
CheckSessionIframe() *Endpoint
AuthMethodPostSupported() bool AuthMethodPostSupported() bool
CodeMethodS256Supported() bool CodeMethodS256Supported() bool
@ -50,9 +49,6 @@ type Configuration interface {
SupportedUILocales() []language.Tag SupportedUILocales() []language.Tag
DeviceAuthorization() DeviceAuthorizationConfig DeviceAuthorization() DeviceAuthorizationConfig
BackChannelLogoutSupported() bool
BackChannelLogoutSessionSupported() bool
} }
type IssuerFromRequest func(r *http.Request) string type IssuerFromRequest func(r *http.Request) string

View file

@ -1,7 +1,7 @@
package op package op
import ( import (
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" "github.com/zitadel/oidc/v3/pkg/crypto"
) )
type Crypto interface { type Crypto interface {

View file

@ -9,12 +9,12 @@ import (
"math/big" "math/big"
"net/http" "net/http"
"net/url" "net/url"
"slices"
"strings" "strings"
"time" "time"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
strs "github.com/zitadel/oidc/v3/pkg/strings"
) )
type DeviceAuthorizationConfig struct { type DeviceAuthorizationConfig struct {
@ -91,7 +91,10 @@ func createDeviceAuthorization(ctx context.Context, req *oidc.DeviceAuthorizatio
} }
config := o.DeviceAuthorization() 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) userCode, err := NewUserCode([]rune(config.UserCode.CharSet), config.UserCode.CharAmount, config.UserCode.DashInterval)
if err != nil { if err != nil {
return nil, NewStatusError(err, http.StatusInternalServerError) 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. // results in a 22 character base64 encoded string.
const RecommendedDeviceCodeBytes = 16 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) { func NewDeviceCode(nBytes int) (string, error) {
bytes := make([]byte, nBytes) 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 return base64.RawURLEncoding.EncodeToString(bytes), nil
} }
@ -276,7 +276,7 @@ func (r *DeviceAuthorizationState) GetAMR() []string {
} }
func (r *DeviceAuthorizationState) GetAudience() []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) r.Audience = append(r.Audience, r.ClientID)
} }
return r.Audience return r.Audience
@ -344,11 +344,10 @@ func CreateDeviceTokenResponse(ctx context.Context, tokenRequest TokenRequest, c
RefreshToken: refreshToken, RefreshToken: refreshToken,
TokenType: oidc.BearerToken, TokenType: oidc.BearerToken,
ExpiresIn: uint64(validity.Seconds()), ExpiresIn: uint64(validity.Seconds()),
Scope: tokenRequest.GetScopes(),
} }
// TODO(v4): remove type assertion // 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) response.IDToken, err = CreateIDToken(ctx, IssuerFromContext(ctx), idTokenRequest, client.IDTokenLifetime(), accessToken, "", creator.Storage(), client)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -13,12 +13,12 @@ import (
"testing" "testing"
"time" "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/muhlemmer/gu"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/example/server/storage"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
) )
func Test_deviceAuthorizationHandler(t *testing.T) { func Test_deviceAuthorizationHandler(t *testing.T) {
@ -145,11 +145,21 @@ func runWithRandReader(r io.Reader, f func()) {
} }
func TestNewDeviceCode(t *testing.T) { func TestNewDeviceCode(t *testing.T) {
for i := 1; i <= 32; i++ { t.Run("reader error", func(t *testing.T) {
got, err := op.NewDeviceCode(i) runWithRandReader(errReader{}, func() {
require.NoError(t, err) _, err := op.NewDeviceCode(16)
assert.Len(t, got, base64.RawURLEncoding.EncodedLen(i)) 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) { func TestNewUserCode(t *testing.T) {

View file

@ -6,8 +6,8 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type DiscoverStorage interface { type DiscoverStorage interface {
@ -45,7 +45,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer), EndSessionEndpoint: config.EndSessionEndpoint().Absolute(issuer),
JwksURI: config.KeysEndpoint().Absolute(issuer), JwksURI: config.KeysEndpoint().Absolute(issuer),
DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer), DeviceAuthorizationEndpoint: config.DeviceAuthorizationEndpoint().Absolute(issuer),
CheckSessionIframe: config.CheckSessionIframe().Absolute(issuer),
ScopesSupported: Scopes(config), ScopesSupported: Scopes(config),
ResponseTypesSupported: ResponseTypes(config), ResponseTypesSupported: ResponseTypes(config),
GrantTypesSupported: GrantTypes(config), GrantTypesSupported: GrantTypes(config),
@ -62,8 +61,6 @@ func CreateDiscoveryConfig(ctx context.Context, config Configuration, storage Di
CodeChallengeMethodsSupported: CodeChallengeMethods(config), CodeChallengeMethodsSupported: CodeChallengeMethods(config),
UILocalesSupported: config.SupportedUILocales(), UILocalesSupported: config.SupportedUILocales(),
RequestParameterSupported: config.RequestObjectSupported(), RequestParameterSupported: config.RequestObjectSupported(),
BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
} }
} }
@ -95,17 +92,11 @@ func createDiscoveryConfigV2(ctx context.Context, config Configuration, storage
CodeChallengeMethodsSupported: CodeChallengeMethods(config), CodeChallengeMethodsSupported: CodeChallengeMethods(config),
UILocalesSupported: config.SupportedUILocales(), UILocalesSupported: config.SupportedUILocales(),
RequestParameterSupported: config.RequestObjectSupported(), RequestParameterSupported: config.RequestObjectSupported(),
BackChannelLogoutSupported: config.BackChannelLogoutSupported(),
BackChannelLogoutSessionSupported: config.BackChannelLogoutSessionSupported(),
} }
} }
func Scopes(c Configuration) []string { func Scopes(c Configuration) []string {
provider, ok := c.(*Provider) return DefaultSupportedScopes // TODO: config
if ok && provider.config.SupportedScopes != nil {
return provider.config.SupportedScopes
}
return DefaultSupportedScopes
} }
func ResponseTypes(c Configuration) []string { func ResponseTypes(c Configuration) []string {
@ -140,7 +131,7 @@ func GrantTypes(c Configuration) []oidc.GrantType {
} }
func SubjectTypes(c Configuration) []string { func SubjectTypes(c Configuration) []string {
return []string{"public"} // TODO: config return []string{"public"} //TODO: config
} }
func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string { func SigAlgorithms(ctx context.Context, storage DiscoverStorage) []string {

View file

@ -11,9 +11,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" "github.com/zitadel/oidc/v3/pkg/op/mock"
) )
func TestDiscover(t *testing.T) { func TestDiscover(t *testing.T) {
@ -81,11 +81,6 @@ func Test_scopes(t *testing.T) {
args{}, args{},
op.DefaultSupportedScopes, op.DefaultSupportedScopes,
}, },
{
"custom scopes",
args{newTestProvider(&op.Config{SupportedScopes: []string{"test1", "test2"}})},
[]string{"test1", "test2"},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View file

@ -3,8 +3,8 @@ package op_test
import ( import (
"testing" "testing"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/op"
) )
func TestEndpoint_Path(t *testing.T) { func TestEndpoint_Path(t *testing.T) {

View file

@ -7,8 +7,8 @@ import (
"log/slog" "log/slog"
"net/http" "net/http"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
type ErrAuthRequest interface { type ErrAuthRequest interface {
@ -46,12 +46,6 @@ func AuthRequestError(w http.ResponseWriter, r *http.Request, authReq ErrAuthReq
return return
} }
e.State = authReq.GetState() e.State = authReq.GetState()
var sessionState string
authRequestSessionState, ok := authReq.(AuthRequestSessionState)
if ok {
sessionState = authRequestSessionState.GetSessionState()
}
e.SessionState = sessionState
var responseMode oidc.ResponseMode var responseMode oidc.ResponseMode
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok { if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
responseMode = rm.GetResponseMode() responseMode = rm.GetResponseMode()
@ -98,12 +92,6 @@ func TryErrorRedirect(ctx context.Context, authReq ErrAuthRequest, parent error,
} }
e.State = authReq.GetState() e.State = authReq.GetState()
var sessionState string
authRequestSessionState, ok := authReq.(AuthRequestSessionState)
if ok {
sessionState = authRequestSessionState.GetSessionState()
}
e.SessionState = sessionState
var responseMode oidc.ResponseMode var responseMode oidc.ResponseMode
if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok { if rm, ok := authReq.(interface{ GetResponseMode() oidc.ResponseMode }); ok {
responseMode = rm.GetResponseMode() responseMode = rm.GetResponseMode()

View file

@ -11,9 +11,9 @@ import (
"strings" "strings"
"testing" "testing"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/schema" "github.com/zitadel/schema"
) )

View file

@ -6,7 +6,7 @@ import (
jose "github.com/go-jose/go-jose/v4" jose "github.com/go-jose/go-jose/v4"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
) )
type KeyProvider interface { type KeyProvider interface {

View file

@ -11,9 +11,9 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op/mock" "github.com/zitadel/oidc/v3/pkg/op/mock"
) )
func TestKeys(t *testing.T) { func TestKeys(t *testing.T) {

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Authorizer) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Authorizer)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -9,9 +9,9 @@ import (
slog "log/slog" slog "log/slog"
reflect "reflect" 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" gomock "github.com/golang/mock/gomock"
http "github.com/zitadel/oidc/v3/pkg/http"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
// MockAuthorizer is a mock of Authorizer interface. // MockAuthorizer is a mock of Authorizer interface.

View file

@ -8,8 +8,8 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/zitadel/schema" "github.com/zitadel/schema"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
func NewAuthorizer(t *testing.T) op.Authorizer { func NewAuthorizer(t *testing.T) op.Authorizer {

View file

@ -5,8 +5,8 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
func NewClient(t *testing.T) op.Client { func NewClient(t *testing.T) op.Client {

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Client) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Client)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -8,9 +8,9 @@ import (
reflect "reflect" reflect "reflect"
time "time" 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" gomock "github.com/golang/mock/gomock"
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
// MockClient is a mock of Client interface. // MockClient is a mock of Client interface.

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Configuration) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Configuration)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -8,8 +8,8 @@ import (
http "net/http" http "net/http"
reflect "reflect" reflect "reflect"
op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
op "github.com/zitadel/oidc/v3/pkg/op"
language "golang.org/x/text/language" 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)) 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. // CodeMethodS256Supported mocks base method.
func (m *MockConfiguration) CodeMethodS256Supported() bool { func (m *MockConfiguration) CodeMethodS256Supported() bool {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: DiscoverStorage) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: DiscoverStorage)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock

View file

@ -1,11 +1,11 @@
package mock package mock
//go:generate go install github.com/golang/mock/mockgen@v1.6.0 //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 ./storage.mock.go github.com/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 ./authorizer.mock.go github.com/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 ./client.mock.go github.com/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 ./glob.mock.go github.com/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 ./configuration.mock.go github.com/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 ./discovery.mock.go github.com/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 ./signer.mock.go github.com/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 ./key.mock.go github.com/zitadel/oidc/v3/pkg/op KeyProvider

View file

@ -3,9 +3,9 @@ package mock
import ( import (
"testing" "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" gomock "github.com/golang/mock/gomock"
"github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
func NewHasRedirectGlobs(t *testing.T) op.HasRedirectGlobs { func NewHasRedirectGlobs(t *testing.T) op.HasRedirectGlobs {

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: HasRedirectGlobs) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: HasRedirectGlobs)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -8,9 +8,9 @@ import (
reflect "reflect" reflect "reflect"
time "time" 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" gomock "github.com/golang/mock/gomock"
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
// MockHasRedirectGlobs is a mock of HasRedirectGlobs interface. // MockHasRedirectGlobs is a mock of HasRedirectGlobs interface.

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: KeyProvider) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: KeyProvider)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -8,8 +8,8 @@ import (
context "context" context "context"
reflect "reflect" reflect "reflect"
op "git.christmann.info/LARA/zitadel-oidc/v3/pkg/op"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
// MockKeyProvider is a mock of KeyProvider interface. // MockKeyProvider is a mock of KeyProvider interface.

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // 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/v3/pkg/op (interfaces: SigningKey,Key)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock

View file

@ -1,5 +1,5 @@
// Code generated by MockGen. DO NOT EDIT. // Code generated by MockGen. DO NOT EDIT.
// Source: git.christmann.info/LARA/zitadel-oidc/v3/pkg/op (interfaces: Storage) // Source: github.com/zitadel/oidc/v3/pkg/op (interfaces: Storage)
// Package mock is a generated GoMock package. // Package mock is a generated GoMock package.
package mock package mock
@ -9,10 +9,10 @@ import (
reflect "reflect" reflect "reflect"
time "time" 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/v4"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
oidc "github.com/zitadel/oidc/v3/pkg/oidc"
op "github.com/zitadel/oidc/v3/pkg/op"
) )
// MockStorage is a mock of Storage interface. // MockStorage is a mock of Storage interface.

View file

@ -8,8 +8,8 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/op" "github.com/zitadel/oidc/v3/pkg/op"
) )
func NewStorage(t *testing.T) op.Storage { func NewStorage(t *testing.T) op.Storage {

View file

@ -14,8 +14,8 @@ import (
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"golang.org/x/text/language" "golang.org/x/text/language"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc" "github.com/zitadel/oidc/v3/pkg/oidc"
) )
const ( const (
@ -158,19 +158,16 @@ func authCallbackPath(o OpenIDProvider) string {
} }
type Config struct { type Config struct {
CryptoKey [32]byte CryptoKey [32]byte
DefaultLogoutRedirectURI string DefaultLogoutRedirectURI string
CodeMethodS256 bool CodeMethodS256 bool
AuthMethodPost bool AuthMethodPost bool
AuthMethodPrivateKeyJWT bool AuthMethodPrivateKeyJWT bool
GrantTypeRefreshToken bool GrantTypeRefreshToken bool
RequestObjectSupported bool RequestObjectSupported bool
SupportedUILocales []language.Tag SupportedUILocales []language.Tag
SupportedClaims []string SupportedClaims []string
SupportedScopes []string DeviceAuthorization DeviceAuthorizationConfig
DeviceAuthorization DeviceAuthorizationConfig
BackChannelLogoutSupported bool
BackChannelLogoutSessionSupported bool
} }
// Endpoints defines endpoint routes. // Endpoints defines endpoint routes.
@ -339,10 +336,6 @@ func (o *Provider) DeviceAuthorizationEndpoint() *Endpoint {
return o.endpoints.DeviceAuthorization return o.endpoints.DeviceAuthorization
} }
func (o *Provider) CheckSessionIframe() *Endpoint {
return o.endpoints.CheckSessionIframe
}
func (o *Provider) KeysEndpoint() *Endpoint { func (o *Provider) KeysEndpoint() *Endpoint {
return o.endpoints.JwksURI return o.endpoints.JwksURI
} }
@ -418,14 +411,6 @@ func (o *Provider) DeviceAuthorization() DeviceAuthorizationConfig {
return o.config.DeviceAuthorization 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 { func (o *Provider) Storage() Storage {
return o.storage return o.storage
} }

View file

@ -11,12 +11,12 @@ import (
"testing" "testing"
"time" "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/muhlemmer/gu"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/example/server/storage"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
@ -232,7 +232,7 @@ func TestRoutes(t *testing.T) {
"scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(), "scope": oidc.SpaceDelimitedArray{oidc.ScopeOpenID, oidc.ScopeOfflineAccess}.String(),
}, },
wantCode: http.StatusOK, 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 // This call will fail. A successful test is already

View file

@ -5,7 +5,7 @@ import (
"errors" "errors"
"net/http" "net/http"
httphelper "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http" httphelper "github.com/zitadel/oidc/v3/pkg/http"
) )
type ProbesFn func(context.Context) error type ProbesFn func(context.Context) error

Some files were not shown because too many files have changed in this diff Show more