Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SDP-928] serve/anchor: Make receiver registration handler tenant aware #117

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev/docker-compose-sdp-anchor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ services:

anchor-platform:
container_name: anchor-platform
image: stellar/anchor-platform:2.1.3
image: stellar/anchor-platform:2.4.0
command: --sep-server --platform-server --platform linux/amd64
ports:
- "8080:8080" # sep-server
Expand Down
2 changes: 1 addition & 1 deletion helmchart/sdp/templates/02.2-deployment-ap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ spec:
- name: {{ .Chart.Name }}-ap
securityContext:
{{- tpl (toYaml .Values.anchorPlatform.deployment.securityContext) . | nindent 12 }}
image: "stellar/anchor-platform:2.1.3"
image: "stellar/anchor-platform:2.4.0"
imagePullPolicy: "IfNotPresent"
{{- if .Values.global.ephemeralDatabase }}
env:
Expand Down
3 changes: 2 additions & 1 deletion internal/anchorplatform/jwt_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ func NewJWTManager(secret string, expirationMiliseconds int64) (*JWTManager, err

// GenerateSEP24Token will generate a JWT token string using the token manager and the provided parameters.
// The parameters are validated before generating the token.
func (manager *JWTManager) GenerateSEP24Token(stellarAccount, stellarMemo, clientDomain, transactionID string) (string, error) {
func (manager *JWTManager) GenerateSEP24Token(stellarAccount, stellarMemo, clientDomain, homeDomain, transactionID string) (string, error) {
subject := stellarAccount
if stellarMemo != "" {
subject = fmt.Sprintf("%s:%s", stellarAccount, stellarMemo)
}

claims := SEP24JWTClaims{
ClientDomainClaim: clientDomain,
HomeDomainClaim: homeDomain,
RegisteredClaims: jwt.RegisteredClaims{
ID: transactionID,
Subject: subject,
Expand Down
5 changes: 3 additions & 2 deletions internal/anchorplatform/jwt_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ func Test_JWTManager_GenerateAndParseSEP24Token(t *testing.T) {
require.NoError(t, err)

// invalid claims
tokenStr, err := jwtManager.GenerateSEP24Token("", "", "test.com", "test-transaction-id")
tokenStr, err := jwtManager.GenerateSEP24Token("", "", "test.com", "test-home-domain.com:3000", "test-transaction-id")
require.EqualError(t, err, "validating SEP24 token claims: stellar account is invalid: strkey is 0 bytes long; minimum valid length is 5")
require.Empty(t, tokenStr)

// valid claims 🎉
tokenStr, err = jwtManager.GenerateSEP24Token("GB54GWWWOSHATX5ALKHBBL2IQBZ2E7TBFO7F7VXKPIW6XANYDK4Y3RRC", "123456", "test.com", "test-transaction-id")
tokenStr, err = jwtManager.GenerateSEP24Token("GB54GWWWOSHATX5ALKHBBL2IQBZ2E7TBFO7F7VXKPIW6XANYDK4Y3RRC", "123456", "test.com", "test-home-domain.com:3000", "test-transaction-id")
require.NoError(t, err)
require.NotEmpty(t, tokenStr)
now := time.Now()
Expand All @@ -50,6 +50,7 @@ func Test_JWTManager_GenerateAndParseSEP24Token(t *testing.T) {
assert.Equal(t, "GB54GWWWOSHATX5ALKHBBL2IQBZ2E7TBFO7F7VXKPIW6XANYDK4Y3RRC", claims.SEP10StellarAccount())
assert.Equal(t, "123456", claims.SEP10StellarMemo())
assert.Equal(t, "test.com", claims.ClientDomain())
assert.Equal(t, "test-home-domain.com:3000", claims.HomeDomain())
assert.True(t, claims.ExpiresAt().After(now.Add(time.Duration(4000*time.Millisecond))))
assert.True(t, claims.ExpiresAt().Before(now.Add(time.Duration(5000*time.Millisecond))))
}
Expand Down
51 changes: 44 additions & 7 deletions internal/anchorplatform/sep24_auth_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"net/http"
"strings"

"github.com/stellar/stellar-disbursement-platform-backend/stellar-multitenant/pkg/tenant"

"github.com/stellar/go/network"
"github.com/stellar/go/support/http/httpdecode"
"github.com/stellar/go/support/log"
"github.com/stellar/stellar-disbursement-platform-backend/internal/serve/httperror"
"github.com/stellar/stellar-disbursement-platform-backend/internal/utils"
)

type ContextType string
Expand All @@ -29,9 +32,10 @@ type SEP24RequestQuery struct {
TransactionID string `query:"transaction_id"`
}

// checkSEP24ClientDomain check if the sep24 token has a client domain and if not check in which network the API is running on,
// only testnet can have an empty client domain.
func checkSEP24ClientDomain(ctx context.Context, sep24Claims *SEP24JWTClaims, networkPassphrase string) error {
// checkSEP24ClientAndHomeDomains check if the sep24 token has a client domain and a home domain,
// if there is not a client domain it checks in which network the API is running on, only testnet can have an empty client domain,
// and home domain is always mandatory.
func checkSEP24ClientAndHomeDomains(ctx context.Context, sep24Claims *SEP24JWTClaims, networkPassphrase string) error {
if sep24Claims.ClientDomain() == "" {
missingDomain := "missing client domain in the token claims"
if networkPassphrase == network.PublicNetworkPassphrase {
Expand All @@ -40,12 +44,17 @@ func checkSEP24ClientDomain(ctx context.Context, sep24Claims *SEP24JWTClaims, ne
}
log.Ctx(ctx).Warn(missingDomain)
}
if sep24Claims.HomeDomain() == "" {
missingDomain := "missing home domain in the token claims"
log.Ctx(ctx).Error(missingDomain)
return fmt.Errorf(missingDomain)
}
return nil
}

// SEP24QueryTokenAuthenticateMiddleware is a middleware that validates if the token passed in as a query
// parameter with ?token={token} is valid for the authenticated endpoints.
func SEP24QueryTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassphrase string) func(http.Handler) http.Handler {
func SEP24QueryTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassphrase string, tenantManager tenant.ManagerInterface) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
Expand Down Expand Up @@ -82,14 +91,28 @@ func SEP24QueryTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassph
return
}

err = checkSEP24ClientDomain(ctx, sep24Claims, networkPassphrase)
err = checkSEP24ClientAndHomeDomains(ctx, sep24Claims, networkPassphrase)
if err != nil {
httperror.BadRequest("", err, nil).Render(rw)
return
}

tenantName, err := utils.ExtractTenantNameFromHostName(sep24Claims.HomeDomain())
if err != nil || tenantName == "" {
httperror.BadRequest("Tenant name not found in SEP24Claims or invalid", err, nil).Render(rw)
return
}

currentTenant, err := tenantManager.GetTenantByName(ctx, tenantName)
if err != nil {
httpErr := fmt.Errorf("failed to load tenant by name for tenant name %s: %w", tenantName, err)
httperror.InternalError(ctx, "Failed to load tenant by name", httpErr, nil).Render(rw)
return
}

// Add the token to the request context
ctx = context.WithValue(ctx, SEP24ClaimsContextKey, sep24Claims)
ctx = tenant.SaveTenantInContext(ctx, currentTenant)
req = req.WithContext(ctx)

next.ServeHTTP(rw, req)
Expand All @@ -99,7 +122,7 @@ func SEP24QueryTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassph

// SEP24HeaderTokenAuthenticateMiddleware is a middleware that validates if the token passed in
// the 'Authorization' header is valid for the authenticated endpoints.
func SEP24HeaderTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassphrase string) func(http.Handler) http.Handler {
func SEP24HeaderTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassphrase string, tenantManager tenant.ManagerInterface) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
Expand Down Expand Up @@ -131,14 +154,28 @@ func SEP24HeaderTokenAuthenticateMiddleware(jwtManager *JWTManager, networkPassp
return
}

err = checkSEP24ClientDomain(ctx, sep24Claims, networkPassphrase)
err = checkSEP24ClientAndHomeDomains(ctx, sep24Claims, networkPassphrase)
if err != nil {
httperror.BadRequest("", err, nil).Render(rw)
return
}

tenantName, err := utils.ExtractTenantNameFromHostName(sep24Claims.HomeDomain())
if err != nil || tenantName == "" {
httperror.BadRequest("Tenant name not found in SEP24Claims or invalid", err, nil).Render(rw)
return
}

currentTenant, err := tenantManager.GetTenantByName(ctx, tenantName)
if err != nil {
httpErr := fmt.Errorf("failed to load tenant by name for tenant name %s: %w", tenantName, err)
httperror.InternalError(ctx, "Failed to load tenant by name", httpErr, nil).Render(rw)
return
}

// Add the token to the request context
ctx = context.WithValue(ctx, SEP24ClaimsContextKey, sep24Claims)
ctx = tenant.SaveTenantInContext(ctx, currentTenant)
req = req.WithContext(ctx)

next.ServeHTTP(rw, req)
Expand Down
Loading
Loading