Merge remote-tracking branch 'og/main'

This commit is contained in:
ORZ (Paul Orzel) 2025-07-17 15:26:12 +02:00
commit 2567f02e81
11 changed files with 237 additions and 37 deletions

View file

@ -73,6 +73,15 @@ CLIENT_ID=web CLIENT_SECRET=secret ISSUER=http://oidc.local:9998/ SCOPES="openid
> 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`)
### Build Tags
The library uses build tags to enable or disable features. The following build tags are available:
| Build Tag | Description |
|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `no_otel` | Disables the OTel instrumentation, which is enabled by default. This is useful if you do not want to use OTel or if you want to use a different instrumentation library. |
### Server configuration ### Server configuration
Example server allows extra configuration using environment variables and could be used for end to Example server allows extra configuration using environment variables and could be used for end to

8
go.mod
View file

@ -5,8 +5,8 @@ go 1.23.7
toolchain go1.24.1 toolchain go1.24.1
require ( require (
github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/bmatcuk/doublestar/v4 v4.9.0
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.2.2
github.com/go-jose/go-jose/v4 v4.0.5 github.com/go-jose/go-jose/v4 v4.0.5
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
@ -21,8 +21,9 @@ require (
github.com/zitadel/logging v0.6.2 github.com/zitadel/logging v0.6.2
github.com/zitadel/schema v1.3.1 github.com/zitadel/schema v1.3.1
go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel v1.29.0
go.opentelemetry.io/otel/trace v1.29.0
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.30.0
golang.org/x/text v0.26.0 golang.org/x/text v0.27.0
) )
require ( require (
@ -32,7 +33,6 @@ require (
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.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect

12
go.sum
View file

@ -1,10 +1,10 @@
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.9.0 h1:DBvuZxjdKkRP/dr4GVV4w2fnmrk5Hxc90T51LZjv0JA=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmatcuk/doublestar/v4 v4.9.0/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.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
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=
@ -88,8 +88,8 @@ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
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=

12
internal/otel/otel.go Normal file
View file

@ -0,0 +1,12 @@
//go:build !no_otel
package otel
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
func Tracer(name string) trace.Tracer {
return otel.Tracer(name)
}

22
internal/otel/shim.go Normal file
View file

@ -0,0 +1,22 @@
//go:build no_otel
package otel
import (
"context"
)
type FakeTracer struct{}
type FakeSpan struct{}
func Tracer(name string) FakeTracer {
return FakeTracer{}
}
func (t FakeTracer) Start(ctx context.Context, _ string) (context.Context, FakeSpan) {
return ctx, FakeSpan{}
}
func (s FakeSpan) End() {
}

View file

@ -12,7 +12,7 @@ import (
"github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"go.opentelemetry.io/otel" "github.com/zitadel/oidc/v3/internal/otel"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto" "git.christmann.info/LARA/zitadel-oidc/v3/pkg/crypto"

View file

@ -3,6 +3,9 @@ package client_test
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/hmac"
cryptoRand "crypto/rand"
"crypto/sha512"
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
@ -18,6 +21,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/google/uuid"
"github.com/gorilla/securecookie"
"github.com/jeremija/gosubmit" "github.com/jeremija/gosubmit"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -42,6 +47,15 @@ var Logger = slog.New(
var CTX context.Context var CTX context.Context
type cookieSpec struct {
cookieHandler *httphelper.CookieHandler
extraCookies []*http.Cookie
}
var defaultCookieSpec = cookieSpec{
cookieHandler: httphelper.NewCookieHandler([]byte("test1234test1234"), []byte("test1234test1234"), httphelper.WithUnsecure()),
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
os.Exit(func() int { os.Exit(func() int {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT)
@ -53,14 +67,65 @@ func TestMain(m *testing.M) {
} }
func TestRelyingPartySession(t *testing.T) { func TestRelyingPartySession(t *testing.T) {
secret := make([]byte, 64)
_, _ = cryptoRand.Read(secret)
hashFunc := sha512.New
requestAwareCookieHandler := httphelper.NewRequestAwareCookieHandler(func(r *http.Request) (*securecookie.SecureCookie, error) {
// Expect a login ID cookie to be set.
loginIDCookie, err := r.Cookie("login_id")
require.NoError(t, err)
// Login ID cookie will contain a UUID.
loginID, err := uuid.Parse(loginIDCookie.Value)
require.NoError(t, err)
// Use a HMAC hash of the secret and login ID (salt).
h := hmac.New(hashFunc, secret)
_, err = h.Write(loginID[:])
require.NoError(t, err)
// Write a separator for the hash key.
_, err = h.Write([]byte("INTEGRITY"))
require.NoError(t, err)
hash := h.Sum(nil) // 64 bytes
// Write a separator for the block key.
_, err = h.Write([]byte("ENCRYPTION"))
require.NoError(t, err)
block := h.Sum(nil)[:32] // Sum is 64 bytes but we only want 32 for AES-256.
return securecookie.New(hash, block), nil
}, httphelper.WithUnsecure())
loginID := uuid.New()
loginIDCookie := &http.Cookie{
Name: "login_id",
Value: loginID.String(),
Secure: false,
SameSite: http.SameSiteLaxMode,
Path: "/",
}
cookieCases := []cookieSpec{
defaultCookieSpec,
{
cookieHandler: requestAwareCookieHandler,
extraCookies: []*http.Cookie{
loginIDCookie,
},
},
}
for _, cookieCase := range cookieCases {
for _, wrapServer := range []bool{false, true} { for _, wrapServer := range []bool{false, true} {
t.Run(fmt.Sprint("wrapServer ", wrapServer), func(t *testing.T) { t.Run(fmt.Sprint("wrapServer ", wrapServer, " requestAwareCookieHandler ", cookieCase.cookieHandler.IsRequestAware()), func(t *testing.T) {
testRelyingPartySession(t, wrapServer) testRelyingPartySession(t, wrapServer, cookieCase)
}) })
} }
} }
}
func testRelyingPartySession(t *testing.T, wrapServer bool) { func testRelyingPartySession(t *testing.T, wrapServer bool, cookieSpec cookieSpec) {
t.Log("------- start example OP ------") t.Log("------- start example OP ------")
targetURL := "http://local-site" targetURL := "http://local-site"
exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL)) exampleStorage := storage.NewStorage(storage.NewUserStore(targetURL))
@ -74,7 +139,7 @@ func testRelyingPartySession(t *testing.T, wrapServer bool) {
clientID := t.Name() + "-" + strconv.FormatInt(seed.Int63(), 25) clientID := t.Name() + "-" + strconv.FormatInt(seed.Int63(), 25)
t.Log("------- run authorization code flow ------") t.Log("------- run authorization code flow ------")
provider, tokens := RunAuthorizationCodeFlow(t, opServer, clientID, "secret") provider, tokens := RunAuthorizationCodeFlow(t, opServer, clientID, "secret", cookieSpec)
t.Log("------- refresh tokens ------") t.Log("------- refresh tokens ------")
@ -220,7 +285,7 @@ func testResourceServerTokenExchange(t *testing.T, wrapServer bool) {
clientSecret := "secret" clientSecret := "secret"
t.Log("------- run authorization code flow ------") t.Log("------- run authorization code flow ------")
provider, tokens := RunAuthorizationCodeFlow(t, opServer, clientID, clientSecret) provider, tokens := RunAuthorizationCodeFlow(t, opServer, clientID, clientSecret, defaultCookieSpec)
resourceServer, err := rs.NewResourceServerClientCredentials(CTX, opServer.URL, clientID, clientSecret) resourceServer, err := rs.NewResourceServerClientCredentials(CTX, opServer.URL, clientID, clientSecret)
require.NoError(t, err, "new resource server") require.NoError(t, err, "new resource server")
@ -275,7 +340,7 @@ func testResourceServerTokenExchange(t *testing.T, wrapServer bool) {
require.Nil(t, tokenExchangeResponse, "token exchange response") require.Nil(t, tokenExchangeResponse, "token exchange response")
} }
func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID, clientSecret string) (provider rp.RelyingParty, tokens *oidc.Tokens[*oidc.IDTokenClaims]) { func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID, clientSecret string, cookieSpec cookieSpec) (provider rp.RelyingParty, tokens *oidc.Tokens[*oidc.IDTokenClaims]) {
targetURL := "http://local-site" targetURL := "http://local-site"
localURL, err := url.Parse(targetURL + "/login?requestID=1234") localURL, err := url.Parse(targetURL + "/login?requestID=1234")
require.NoError(t, err, "local url") require.NoError(t, err, "local url")
@ -294,8 +359,6 @@ func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID,
} }
t.Log("------- create RP ------") t.Log("------- create RP ------")
key := []byte("test1234test1234")
cookieHandler := httphelper.NewCookieHandler(key, key, httphelper.WithUnsecure())
provider, err = rp.NewRelyingPartyOIDC( provider, err = rp.NewRelyingPartyOIDC(
CTX, CTX,
opServer.URL, opServer.URL,
@ -303,7 +366,7 @@ func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID,
clientSecret, clientSecret,
targetURL, targetURL,
[]string{"openid", "email", "profile", "offline_access"}, []string{"openid", "email", "profile", "offline_access"},
rp.WithPKCE(cookieHandler), rp.WithPKCE(cookieSpec.cookieHandler),
rp.WithAuthStyle(oauth2.AuthStyleInHeader), rp.WithAuthStyle(oauth2.AuthStyleInHeader),
rp.WithVerifierOpts( rp.WithVerifierOpts(
rp.WithIssuedAtOffset(5*time.Second), rp.WithIssuedAtOffset(5*time.Second),
@ -317,6 +380,11 @@ func RunAuthorizationCodeFlow(t *testing.T, opServer *httptest.Server, clientID,
state := "state-" + strconv.FormatInt(seed.Int63(), 25) state := "state-" + strconv.FormatInt(seed.Int63(), 25)
capturedW := httptest.NewRecorder() capturedW := httptest.NewRecorder()
get := httptest.NewRequest("GET", localURL.String(), nil) get := httptest.NewRequest("GET", localURL.String(), nil)
for _, cookie := range cookieSpec.extraCookies {
get.AddCookie(cookie)
http.SetCookie(capturedW, cookie)
}
rp.AuthURLHandler(func() string { return state }, provider, rp.AuthURLHandler(func() string { return state }, provider,
rp.WithPromptURLParam("Hello, World!", "Goodbye, World!"), rp.WithPromptURLParam("Hello, World!", "Goodbye, World!"),
rp.WithURLParam("custom", "param"), rp.WithURLParam("custom", "param"),

View file

@ -410,12 +410,19 @@ func AuthURLHandler(stateFn func() string, rp RelyingParty, urlParam ...URLParam
} }
state := stateFn() state := stateFn()
if err := trySetStateCookie(w, state, rp); err != nil { if err := trySetStateCookie(r, w, state, rp); err != nil {
unauthorizedError(w, r, "failed to create state cookie: "+err.Error(), state, rp) unauthorizedError(w, r, "failed to create state cookie: "+err.Error(), state, rp)
return return
} }
if rp.IsPKCE() { if rp.IsPKCE() {
codeChallenge, err := GenerateAndStoreCodeChallenge(w, rp) var codeChallenge string
var err error
if rp.CookieHandler().IsRequestAware() {
codeChallenge, err = GenerateAndStoreCodeChallengeWithRequest(r, w, rp)
} else {
codeChallenge, err = GenerateAndStoreCodeChallenge(w, rp)
}
if err != nil { if err != nil {
unauthorizedError(w, r, "failed to create code challenge: "+err.Error(), state, rp) unauthorizedError(w, r, "failed to create code challenge: "+err.Error(), state, rp)
return return
@ -436,6 +443,15 @@ func GenerateAndStoreCodeChallenge(w http.ResponseWriter, rp RelyingParty) (stri
return oidc.NewSHACodeChallenge(codeVerifier), nil return oidc.NewSHACodeChallenge(codeVerifier), nil
} }
// GenerateAndStoreCodeChallenge generates a PKCE code challenge and stores its verifier into a secure cookie
func GenerateAndStoreCodeChallengeWithRequest(r *http.Request, w http.ResponseWriter, rp RelyingParty) (string, error) {
codeVerifier := base64.RawURLEncoding.EncodeToString([]byte(uuid.New().String()))
if err := rp.CookieHandler().SetRequestAwareCookie(r, w, pkceCode, codeVerifier); err != nil {
return "", err
}
return oidc.NewSHACodeChallenge(codeVerifier), nil
}
// ErrMissingIDToken is returned when an id_token was expected, // ErrMissingIDToken is returned when an id_token was expected,
// but not received in the token response. // but not received in the token response.
var ErrMissingIDToken = errors.New("id_token missing") var ErrMissingIDToken = errors.New("id_token missing")
@ -607,9 +623,16 @@ func Userinfo[U SubjectGetter](ctx context.Context, token, tokenType, subject st
return userinfo, nil return userinfo, nil
} }
func trySetStateCookie(w http.ResponseWriter, state string, rp RelyingParty) error { func trySetStateCookie(r *http.Request, w http.ResponseWriter, state string, rp RelyingParty) error {
if rp.CookieHandler() != nil { if rp.CookieHandler() != nil {
if err := rp.CookieHandler().SetCookie(w, stateParam, state); err != nil { var err error
if rp.CookieHandler().IsRequestAware() {
err = rp.CookieHandler().SetRequestAwareCookie(r, w, stateParam, state)
} else {
err = rp.CookieHandler().SetCookie(w, stateParam, state)
}
if err != nil {
return err return err
} }
} }

View file

@ -9,6 +9,7 @@ import (
type CookieHandler struct { type CookieHandler struct {
securecookie *securecookie.SecureCookie securecookie *securecookie.SecureCookie
secureCookieFunc func(r *http.Request) (*securecookie.SecureCookie, error)
secureOnly bool secureOnly bool
sameSite http.SameSite sameSite http.SameSite
maxAge int maxAge int
@ -30,6 +31,21 @@ func NewCookieHandler(hashKey, encryptKey []byte, opts ...CookieHandlerOpt) *Coo
return c return c
} }
func NewRequestAwareCookieHandler(secureCookieFunc func(r *http.Request) (*securecookie.SecureCookie, error), opts ...CookieHandlerOpt) *CookieHandler {
c := &CookieHandler{
secureCookieFunc: secureCookieFunc,
secureOnly: true,
sameSite: http.SameSiteLaxMode,
path: "/",
}
for _, opt := range opts {
opt(c)
}
return c
}
type CookieHandlerOpt func(*CookieHandler) type CookieHandlerOpt func(*CookieHandler)
func WithUnsecure() CookieHandlerOpt { func WithUnsecure() CookieHandlerOpt {
@ -47,6 +63,10 @@ func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
func WithMaxAge(maxAge int) CookieHandlerOpt { func WithMaxAge(maxAge int) CookieHandlerOpt {
return func(c *CookieHandler) { return func(c *CookieHandler) {
c.maxAge = maxAge c.maxAge = maxAge
if c.IsRequestAware() {
return
}
c.securecookie.MaxAge(maxAge) c.securecookie.MaxAge(maxAge)
} }
} }
@ -68,8 +88,17 @@ func (c *CookieHandler) CheckCookie(r *http.Request, name string) (string, error
if err != nil { if err != nil {
return "", err return "", err
} }
secureCookie := c.securecookie
if c.IsRequestAware() {
secureCookie, err = c.secureCookieFunc(r)
if err != nil {
return "", err
}
}
var value string var value string
if err := c.securecookie.Decode(name, cookie.Value, &value); err != nil { if err := secureCookie.Decode(name, cookie.Value, &value); err != nil {
return "", err return "", err
} }
return value, nil return value, nil
@ -87,6 +116,10 @@ func (c *CookieHandler) CheckQueryCookie(r *http.Request, name string) (string,
} }
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, value string) error { func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, value string) error {
if c.IsRequestAware() {
return errors.New("Cookie handler is request aware")
}
encoded, err := c.securecookie.Encode(name, value) encoded, err := c.securecookie.Encode(name, value)
if err != nil { if err != nil {
return err return err
@ -104,6 +137,35 @@ func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, value string) err
return nil return nil
} }
func (c *CookieHandler) SetRequestAwareCookie(r *http.Request, w http.ResponseWriter, name string, value string) error {
if !c.IsRequestAware() {
return errors.New("Cookie handler is not request aware")
}
secureCookie, err := c.secureCookieFunc(r)
if err != nil {
return err
}
encoded, err := secureCookie.Encode(name, value)
if err != nil {
return err
}
http.SetCookie(w, &http.Cookie{
Name: name,
Value: encoded,
Domain: c.domain,
Path: c.path,
MaxAge: c.maxAge,
HttpOnly: true,
Secure: c.secureOnly,
SameSite: c.sameSite,
})
return nil
}
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) { func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: name, Name: name,
@ -116,3 +178,7 @@ func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
SameSite: c.sameSite, SameSite: c.sameSite,
}) })
} }
func (c *CookieHandler) IsRequestAware() bool {
return c.secureCookieFunc != nil
}

View file

@ -240,6 +240,6 @@ type ClientCredentialsRequest struct {
Scope SpaceDelimitedArray `schema:"scope"` Scope SpaceDelimitedArray `schema:"scope"`
ClientID string `schema:"client_id"` ClientID string `schema:"client_id"`
ClientSecret string `schema:"client_secret"` ClientSecret string `schema:"client_secret"`
ClientAssertion string `schema:"client_assertion"` ClientAssertion string `schema:"client_assertion,omitempty"`
ClientAssertionType string `schema:"client_assertion_type"` ClientAssertionType string `schema:"client_assertion_type,omitempty"`
} }

View file

@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
jose "github.com/go-jose/go-jose/v4" "github.com/go-jose/go-jose/v4"
"github.com/rs/cors" "github.com/rs/cors"
"github.com/zitadel/oidc/v3/internal/otel"
"github.com/zitadel/schema" "github.com/zitadel/schema"
"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 "git.christmann.info/LARA/zitadel-oidc/v3/pkg/http"