Skip to content

Commit

Permalink
txnbuild: add muxed account & memo support to SEP-10 utility functions (
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeUrban authored Jan 30, 2023
2 parents b8cba70 + 0f31bdd commit 295e520
Show file tree
Hide file tree
Showing 9 changed files with 722 additions and 98 deletions.
20 changes: 18 additions & 2 deletions exp/services/webauth/internal/serve/challenge.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package serve

import (
"net/http"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -33,7 +34,9 @@ func (h challengeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
queryValues := r.URL.Query()

account := queryValues.Get("account")
if !strkey.IsValidEd25519PublicKey(account) {
isStellarAccount := strkey.IsValidEd25519PublicKey(account)
isMuxedAccount := strkey.IsValidMuxedAccountEd25519PublicKey(account)
if !isStellarAccount && !isMuxedAccount {
badRequest.Render(w)
return
}
Expand All @@ -57,17 +60,30 @@ func (h challengeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
homeDomain = h.HomeDomains[0]
}

var memo *txnbuild.MemoID
memoParam := queryValues.Get("memo")
if memoParam != "" {
memoInt, err := strconv.ParseUint(memoParam, 10, 64)
if err != nil {
badRequest.Render(w)
return
}
memoId := txnbuild.MemoID(memoInt)
memo = &memoId
}

tx, err := txnbuild.BuildChallengeTx(
h.SigningKey.Seed(),
account,
h.Domain,
homeDomain,
h.NetworkPassphrase,
h.ChallengeExpiresIn,
memo,
)
if err != nil {
h.Logger.Ctx(ctx).WithStack(err).Error(err)
serverError.Render(w)
badRequest.Render(w)
return
}

Expand Down
106 changes: 106 additions & 0 deletions exp/services/webauth/internal/serve/challenge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (

"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
"github.com/stellar/go/strkey"
supportlog "github.com/stellar/go/support/log"
"github.com/stellar/go/txnbuild"
"github.com/stellar/go/xdr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -185,3 +187,107 @@ func TestChallenge_invalidHomeDomain(t *testing.T) {
require.NoError(t, err)
assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(body))
}

func TestChallengeWithMemo(t *testing.T) {
serverKey := keypair.MustRandom()
account := keypair.MustRandom()

h := challengeHandler{
Logger: supportlog.DefaultLogger,
NetworkPassphrase: network.TestNetworkPassphrase,
SigningKey: serverKey,
ChallengeExpiresIn: time.Minute,
Domain: "webauthdomain",
HomeDomains: []string{"testdomain"},
}

r := httptest.NewRequest("GET", "/?account="+account.Address()+"&memo=1", nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
resp := w.Result()

require.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type"))

res := struct {
Transaction string `json:"transaction"`
NetworkPassphrase string `json:"network_passphrase"`
}{}
err := json.NewDecoder(resp.Body).Decode(&res)
require.NoError(t, err)

var tx xdr.TransactionEnvelope
err = xdr.SafeUnmarshalBase64(res.Transaction, &tx)
require.NoError(t, err)

memo, err := txnbuild.MemoID(1).ToXDR()
require.NoError(t, err)
require.Equal(t, tx.Memo(), memo)
}

func TestChallengeWithBadMemo(t *testing.T) {
serverKey := keypair.MustRandom()
account := keypair.MustRandom()

h := challengeHandler{
Logger: supportlog.DefaultLogger,
NetworkPassphrase: network.TestNetworkPassphrase,
SigningKey: serverKey,
ChallengeExpiresIn: time.Minute,
Domain: "webauthdomain",
HomeDomains: []string{"testdomain"},
}

r := httptest.NewRequest("GET", "/?account="+account.Address()+"&memo=test", nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
resp := w.Result()

require.Equal(t, http.StatusBadRequest, resp.StatusCode)
assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type"))

body, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
assert.JSONEq(t, `{"error":"The request was invalid in some way."}`, string(body))
}

func TestChallengeWithMuxedAccount(t *testing.T) {
serverKey := keypair.MustRandom()
account := keypair.MustRandom()

muxedAccount := strkey.MuxedAccount{}
muxedAccount.SetAccountID(account.Address())
muxedAccount.SetID(1)
muxedAccountAddress, err := muxedAccount.Address()
require.NoError(t, err)

h := challengeHandler{
Logger: supportlog.DefaultLogger,
NetworkPassphrase: network.TestNetworkPassphrase,
SigningKey: serverKey,
ChallengeExpiresIn: time.Minute,
Domain: "webauthdomain",
HomeDomains: []string{"testdomain"},
}

r := httptest.NewRequest("GET", "/?account="+muxedAccountAddress, nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
resp := w.Result()

require.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type"))

res := struct {
Transaction string `json:"transaction"`
NetworkPassphrase string `json:"network_passphrase"`
}{}
err = json.NewDecoder(resp.Body).Decode(&res)
require.NoError(t, err)

var tx xdr.TransactionEnvelope
err = xdr.SafeUnmarshalBase64(res.Transaction, &tx)
require.NoError(t, err)

require.Equal(t, tx.Operations()[0].SourceAccount.Address(), muxedAccountAddress)
}
29 changes: 26 additions & 3 deletions exp/services/webauth/internal/serve/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package serve

import (
"net/http"
"strconv"
"strings"
"time"

Expand All @@ -11,6 +12,7 @@ import (
supportlog "github.com/stellar/go/support/log"
"github.com/stellar/go/support/render/httpjson"
"github.com/stellar/go/txnbuild"
"github.com/stellar/go/xdr"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
Expand Down Expand Up @@ -52,9 +54,10 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
clientAccountID string
signingAddress *keypair.FromAddress
homeDomain string
memo *txnbuild.MemoID
)
for _, s := range h.SigningAddresses {
tx, clientAccountID, homeDomain, err = txnbuild.ReadChallengeTx(req.Transaction, s.Address(), h.NetworkPassphrase, h.Domain, h.HomeDomains)
tx, clientAccountID, homeDomain, memo, err = txnbuild.ReadChallengeTx(req.Transaction, s.Address(), h.NetworkPassphrase, h.Domain, h.HomeDomains)
if err == nil {
signingAddress = s
break
Expand All @@ -80,10 +83,20 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
WithField("tx", hash).
WithField("account", clientAccountID).
WithField("serversigner", signingAddress.Address()).
WithField("homedomain", homeDomain)
WithField("homedomain", homeDomain).
WithField("memo", memo)

l.Info("Start verifying challenge transaction.")

muxedAccount, err := xdr.AddressToMuxedAccount(clientAccountID)
if err != nil {
badRequest.Render(w)
return
}
if muxedAccount.Type == xdr.CryptoKeyTypeKeyTypeMuxedEd25519 {
clientAccountID = muxedAccount.ToAccountId().Address()
}

var clientAccountExists bool
clientAccount, err := h.HorizonClient.AccountDetail(horizonclient.AccountRequest{AccountID: clientAccountID})
switch {
Expand Down Expand Up @@ -140,10 +153,20 @@ func (h tokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

var sub string
if muxedAccount.Type == xdr.CryptoKeyTypeKeyTypeEd25519 {
sub = clientAccountID
if memo != nil {
sub += ":" + strconv.FormatUint(uint64(*memo), 10)
}
} else {
sub = muxedAccount.Address()
}

issuedAt := time.Unix(tx.Timebounds().MinTime, 0)
claims := jwt.Claims{
Issuer: h.JWTIssuer,
Subject: clientAccountID,
Subject: sub,
IssuedAt: jwt.NewNumericDate(issuedAt),
Expiry: jwt.NewNumericDate(issuedAt.Add(h.JWTExpiresIn)),
}
Expand Down
Loading

0 comments on commit 295e520

Please sign in to comment.