zitadel-oidc/pkg/client/profile/jwt_profile.go

118 lines
4 KiB
Go

package profile
import (
"context"
"net/http"
"time"
jose "github.com/go-jose/go-jose/v4"
"golang.org/x/oauth2"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/client"
"git.christmann.info/LARA/zitadel-oidc/v3/pkg/oidc"
)
type TokenSource interface {
oauth2.TokenSource
TokenCtx(context.Context) (*oauth2.Token, error)
}
// jwtProfileTokenSource implement the oauth2.TokenSource
// it will request a token using the OAuth2 JWT Profile Grant
// therefore sending an `assertion` by signing a JWT with the provided private key
type jwtProfileTokenSource struct {
clientID string
audience []string
signer jose.Signer
scopes []string
httpClient *http.Client
tokenEndpoint string
}
// NewJWTProfileTokenSourceFromKeyFile returns an implementation of TokenSource
// It will request a token using the OAuth2 JWT Profile Grant,
// therefore sending an `assertion` by singing a JWT with the provided private key from jsonFile.
//
// The passed context is only used for the call to the Discover endpoint.
func NewJWTProfileTokenSourceFromKeyFile(ctx context.Context, issuer, jsonFile string, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
keyData, err := client.ConfigFromKeyFile(jsonFile)
if err != nil {
return nil, err
}
return NewJWTProfileTokenSource(ctx, issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
}
// NewJWTProfileTokenSourceFromKeyFileData returns an implementation of oauth2.TokenSource
// It will request a token using the OAuth2 JWT Profile Grant,
// therefore sending an `assertion` by singing a JWT with the provided private key in jsonData.
//
// The passed context is only used for the call to the Discover endpoint.
func NewJWTProfileTokenSourceFromKeyFileData(ctx context.Context, issuer string, jsonData []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
keyData, err := client.ConfigFromKeyFileData(jsonData)
if err != nil {
return nil, err
}
return NewJWTProfileTokenSource(ctx, issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
}
// NewJWTProfileSource returns an implementation of oauth2.TokenSource
// It will request a token using the OAuth2 JWT Profile Grant,
// therefore sending an `assertion` by singing a JWT with the provided private key.
//
// The passed context is only used for the call to the Discover endpoint.
func NewJWTProfileTokenSource(ctx context.Context, issuer, clientID, keyID string, key []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
signer, err := client.NewSignerFromPrivateKeyByte(key, keyID)
if err != nil {
return nil, err
}
source := &jwtProfileTokenSource{
clientID: clientID,
audience: []string{issuer},
signer: signer,
scopes: scopes,
httpClient: http.DefaultClient,
}
for _, opt := range options {
opt(source)
}
if source.tokenEndpoint == "" {
config, err := client.Discover(ctx, issuer, source.httpClient)
if err != nil {
return nil, err
}
source.tokenEndpoint = config.TokenEndpoint
}
return source, nil
}
func WithHTTPClient(client *http.Client) func(source *jwtProfileTokenSource) {
return func(source *jwtProfileTokenSource) {
source.httpClient = client
}
}
func WithStaticTokenEndpoint(issuer, tokenEndpoint string) func(source *jwtProfileTokenSource) {
return func(source *jwtProfileTokenSource) {
source.tokenEndpoint = tokenEndpoint
}
}
func (j *jwtProfileTokenSource) TokenEndpoint() string {
return j.tokenEndpoint
}
func (j *jwtProfileTokenSource) HttpClient() *http.Client {
return j.httpClient
}
func (j *jwtProfileTokenSource) Token() (*oauth2.Token, error) {
return j.TokenCtx(context.Background())
}
func (j *jwtProfileTokenSource) TokenCtx(ctx context.Context) (*oauth2.Token, error) {
assertion, err := client.SignedJWTProfileAssertion(j.clientID, j.audience, time.Hour, j.signer)
if err != nil {
return nil, err
}
return client.JWTProfileExchange(ctx, oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), j)
}