Files
upstream-ory-kratos/selfservice/strategy/oidc/provider_amazon.go
upstream-mirror c60cc7ca75
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:19:12 +05:00

142 lines
4.2 KiB
Go

// Copyright © 2025 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package oidc
import (
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"slices"
"testing"
"github.com/hashicorp/go-retryablehttp"
"github.com/pkg/errors"
"golang.org/x/oauth2"
"golang.org/x/oauth2/amazon"
"github.com/ory/herodot"
"github.com/ory/x/httpx"
"github.com/ory/x/otelx"
)
var _ OAuth2Provider = (*ProviderAmazon)(nil)
var amazonSupportedScopes = []string{"profile", "profile:user_id", "postal_code"}
type ProviderAmazon struct {
*ProviderGenericOIDC
amazonProfileURL string // Only overriden in tests.
}
type amazonProfileResponse struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Name string `json:"name"`
PostalCode string `json:"postal_code"`
}
func NewProviderAmazon(
config *Configuration,
reg Dependencies,
) Provider {
config.IssuerURL = amazon.Endpoint.AuthURL
const amazonProfileURL string = "https://api.amazon.com/user/profile"
return &ProviderAmazon{
ProviderGenericOIDC: &ProviderGenericOIDC{
config: config,
reg: reg,
},
amazonProfileURL: amazonProfileURL,
}
}
func (p *ProviderAmazon) SetProfileURL(t *testing.T, url string) {
p.amazonProfileURL = url
}
func (p *ProviderAmazon) Config() *Configuration {
return p.config
}
func (p *ProviderAmazon) oauth2(ctx context.Context) *oauth2.Config {
return &oauth2.Config{
ClientID: p.config.ClientID,
ClientSecret: p.config.ClientSecret,
Endpoint: amazon.Endpoint,
Scopes: p.config.Scope,
RedirectURL: p.config.Redir(p.reg.Config().OIDCRedirectURIBase(ctx)),
}
}
func (p *ProviderAmazon) validateConfiguration() error {
for _, s := range p.config.Scope {
if !slices.Contains(amazonSupportedScopes, s) {
return errors.WithStack(
herodot.ErrMisconfiguration().WithReasonf("scope %s not supported. Supported: %+v", s, amazonSupportedScopes))
}
}
if p.config.PKCE == "auto" {
return errors.WithStack(herodot.ErrMisconfiguration().WithReason("pkce:auto is not supported because Amazon does not support PKCE discovery"))
}
return nil
}
func (p *ProviderAmazon) OAuth2(ctx context.Context) (*oauth2.Config, error) {
// This is as good a place as any to validate the configuration.
if err := p.validateConfiguration(); err != nil {
return nil, err
}
return p.oauth2(ctx), nil
}
func (p *ProviderAmazon) AuthCodeURLOptions(r ider) []oauth2.AuthCodeOption {
return []oauth2.AuthCodeOption{}
}
func (p *ProviderAmazon) Claims(ctx context.Context, exchange *oauth2.Token, query url.Values) (_ *Claims, err error) {
ctx, span := p.reg.Tracer(ctx).Tracer().Start(ctx, "selfservice.strategy.oidc.ProviderAmazon.Claims")
defer otelx.End(span, &err)
ctx, client := httpx.SetOAuth2(ctx, p.reg.HTTPClient(ctx), p.oauth2(ctx), exchange)
req, err := retryablehttp.NewRequestWithContext(ctx, http.MethodGet, p.amazonProfileURL, nil)
if err != nil {
return nil, errors.WithStack(herodot.ErrInternalServerError().WithReason("failed to create HTTP request").WithDetail("url", p.amazonProfileURL).WithError(err.Error()))
}
req.Header.Set("x-amz-access-token", exchange.AccessToken)
resp, err := client.Do(req)
if err != nil {
return nil, errors.WithStack(herodot.ErrUpstreamError().WithReason("failed to make HTTP request").WithDetail("url", p.amazonProfileURL).WithError(err.Error()))
}
defer func() { _ = resp.Body.Close() }()
body := io.LimitReader(resp.Body, 64*1024) // 64 KiB
if resp.StatusCode != http.StatusOK {
rawResponse, _ := io.ReadAll(body)
return nil, errors.WithStack(herodot.ErrUpstreamError().WithReason("non 200 response").WithDetail("url", p.amazonProfileURL).WithDetail("external_error", string(rawResponse)).
WithDetail("external_status_code", resp.StatusCode))
}
profile := amazonProfileResponse{}
if err := json.NewDecoder(body).Decode(&profile); err != nil {
rawResponse, _ := io.ReadAll(body)
return nil, errors.WithStack(herodot.ErrUpstreamError().WithDetail("url", p.amazonProfileURL).WithDetail("raw_response", rawResponse).WithError(err.Error()))
}
claims := &Claims{
Subject: profile.UserID,
Issuer: amazon.Endpoint.TokenURL,
Name: profile.Name,
Email: profile.Email,
Zoneinfo: profile.PostalCode,
}
return claims, nil
}