Skip to content

Commit

Permalink
rpc: add migration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
patrislav committed Aug 2, 2024
1 parent 9feabfc commit 5ecc38b
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 4 deletions.
25 changes: 21 additions & 4 deletions rpc/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,24 +378,29 @@ func newAccount(t *testing.T, tnt *data.Tenant, enc *enclave.Enclave, identity p
encryptedKey, algorithm, ciphertext, err := crypto.EncryptData(context.Background(), att, "27ebbde0-49d2-4cb6-ad78-4f2c24fe7b79", payload)
require.NoError(t, err)

email := "[email protected]"
return &data.Account{
ProjectID: tnt.ProjectID,
Identity: data.Identity(identity),
UserID: payload.UserID,
Email: "[email protected]",
ProjectScopedEmail: fmt.Sprintf("%d|[email protected]", tnt.ProjectID),
Email: email,
ProjectScopedEmail: fmt.Sprintf("%d|%s", tnt.ProjectID, email),
EncryptedKey: encryptedKey,
Algorithm: algorithm,
Ciphertext: ciphertext,
CreatedAt: payload.CreatedAt,
}
}

func newOIDCIdentity(issuer string) proto.Identity {
func newOIDCIdentity(issuer string, optSubject ...string) proto.Identity {
subject := "SUBJECT"
if len(optSubject) > 0 {
subject = optSubject[0]
}
return proto.Identity{
Type: proto.IdentityType_OIDC,
Issuer: issuer,
Subject: "SUBJECT",
Subject: subject,
}
}

Expand All @@ -407,6 +412,18 @@ func newEmailIdentity(email string) proto.Identity {
}
}

func newStytchIdentity(stytchProjectID string, optSubject ...string) proto.Identity {
subject := "SUBJECT"
if len(optSubject) > 0 {
subject = optSubject[0]
}
return proto.Identity{
Type: proto.IdentityType_Stytch,
Issuer: stytchProjectID,
Subject: subject,
}
}

func newSessionFromData(t *testing.T, tnt *data.Tenant, enc *enclave.Enclave, payload *proto.SessionData) *data.Session {
att, err := enc.GetAttestation(context.Background(), nil)
require.NoError(t, err)
Expand Down
233 changes: 233 additions & 0 deletions rpc/migrations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package rpc_test

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"

"github.com/0xsequence/ethkit/ethwallet"
"github.com/0xsequence/ethkit/go-ethereum/common/hexutil"
"github.com/0xsequence/ethkit/go-ethereum/crypto"
"github.com/0xsequence/go-sequence/intents"
"github.com/0xsequence/waas-authenticator/config"
"github.com/0xsequence/waas-authenticator/data"
"github.com/0xsequence/waas-authenticator/proto"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMigrationOIDCToStytch(t *testing.T) {
t.Run("WithoutConfig", func(t *testing.T) {
t.Run("NoContinuousMigration", func(t *testing.T) {
ctx := context.Background()

exp := time.Now().Add(120 * time.Second)
tokBuilderFn := func(b *jwt.Builder, url string) {
b.Expiration(exp)
}

issuer, tok, closeJWKS := issueAccessTokenAndRunJwksServer(t, tokBuilderFn)
defer closeJWKS()

svc := initRPC(t, func(cfg *config.Config) {
cfg.Migrations.OIDCToStytch = []config.OIDCToStytchConfig{
{
ProjectID: currentProjectID.Load() + 1,
StytchProjectID: "TEST",
FromIssuer: "FAKE_ISSUER",
},
}
})
tenant, _ := newTenant(t, svc.Enclave, issuer)
require.NoError(t, svc.Tenants.Add(ctx, tenant))

sessWallet, err := ethwallet.NewWalletFromRandomEntropy()
require.NoError(t, err)
signingSession := intents.NewSessionP256K1(sessWallet)

srv := httptest.NewServer(svc.Handler())
defer srv.Close()

c := proto.NewWaasAuthenticatorClient(srv.URL, http.DefaultClient)
header := make(http.Header)
header.Set("X-Sequence-Project", strconv.Itoa(int(tenant.ProjectID)))
ctx, err = proto.WithHTTPRequestHeaders(context.Background(), header)
require.NoError(t, err)

hashedToken := hexutil.Encode(crypto.Keccak256([]byte(tok)))
verifier := hashedToken + ";" + strconv.Itoa(int(exp.Unix()))
initiateAuth := generateSignedIntent(t, intents.IntentName_initiateAuth, intents.IntentDataInitiateAuth{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_OIDC,
Verifier: verifier,
}, signingSession)
initRes, err := c.SendIntent(ctx, initiateAuth)
require.NoError(t, err)
assert.Equal(t, proto.IntentResponseCode_authInitiated, initRes.Code)

b, err := json.Marshal(initRes.Data)
require.NoError(t, err)
var initResData intents.IntentResponseAuthInitiated
require.NoError(t, json.Unmarshal(b, &initResData))

registerSession := generateSignedIntent(t, intents.IntentName_openSession, intents.IntentDataOpenSession{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_OIDC,
Verifier: verifier,
Answer: tok,
}, signingSession)
sess, registerRes, err := c.RegisterSession(ctx, registerSession, "Friendly name")
require.NoError(t, err)
assert.Equal(t, "OIDC:"+issuer+"#subject", sess.Identity.String())
assert.Equal(t, proto.IntentResponseCode_sessionOpened, registerRes.Code)

accs, _, err := svc.Accounts.ListByProjectAndIdentity(ctx, data.Page{}, tenant.ProjectID, proto.IdentityType_Stytch, "")
require.NoError(t, err)
assert.Len(t, accs, 0)
})
})

t.Run("ContinuousMigration", func(t *testing.T) {
ctx := context.Background()

exp := time.Now().Add(120 * time.Second)
tokBuilderFn := func(b *jwt.Builder, url string) {
b.Expiration(exp)
}

issuer, tok, closeJWKS := issueAccessTokenAndRunJwksServer(t, tokBuilderFn)
defer closeJWKS()

svc := initRPC(t, func(cfg *config.Config) {
cfg.Migrations.OIDCToStytch = []config.OIDCToStytchConfig{
{
ProjectID: currentProjectID.Load() + 1,
StytchProjectID: "TEST",
FromIssuer: issuer,
},
}
})
tenant, _ := newTenant(t, svc.Enclave, issuer)
require.NoError(t, svc.Tenants.Add(ctx, tenant))

sessWallet, err := ethwallet.NewWalletFromRandomEntropy()
require.NoError(t, err)
signingSession := intents.NewSessionP256K1(sessWallet)

srv := httptest.NewServer(svc.Handler())
defer srv.Close()

c := proto.NewWaasAuthenticatorClient(srv.URL, http.DefaultClient)
header := make(http.Header)
header.Set("X-Sequence-Project", strconv.Itoa(int(tenant.ProjectID)))
ctx, err = proto.WithHTTPRequestHeaders(context.Background(), header)
require.NoError(t, err)

hashedToken := hexutil.Encode(crypto.Keccak256([]byte(tok)))
verifier := hashedToken + ";" + strconv.Itoa(int(exp.Unix()))
initiateAuth := generateSignedIntent(t, intents.IntentName_initiateAuth, intents.IntentDataInitiateAuth{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_OIDC,
Verifier: verifier,
}, signingSession)
initRes, err := c.SendIntent(ctx, initiateAuth)
require.NoError(t, err)
assert.Equal(t, proto.IntentResponseCode_authInitiated, initRes.Code)

b, err := json.Marshal(initRes.Data)
require.NoError(t, err)
var initResData intents.IntentResponseAuthInitiated
require.NoError(t, json.Unmarshal(b, &initResData))

registerSession := generateSignedIntent(t, intents.IntentName_openSession, intents.IntentDataOpenSession{
SessionID: signingSession.SessionID(),
IdentityType: intents.IdentityType_OIDC,
Verifier: verifier,
Answer: tok,
}, signingSession)
sess, registerRes, err := c.RegisterSession(ctx, registerSession, "Friendly name")
require.NoError(t, err)
assert.Equal(t, "OIDC:"+issuer+"#subject", sess.Identity.String())
assert.Equal(t, proto.IntentResponseCode_sessionOpened, registerRes.Code)

expectedIdentity := proto.Identity{
Type: proto.IdentityType_Stytch,
Issuer: "TEST",
Subject: "subject",
}
accs, _, err := svc.Accounts.ListByProjectAndIdentity(ctx, data.Page{}, tenant.ProjectID, proto.IdentityType_Stytch, "")
require.NoError(t, err)
require.Len(t, accs, 1)
assert.Equal(t, expectedIdentity, proto.Identity(accs[0].Identity))
assert.Equal(t, sess.UserID, accs[0].UserID)
})

t.Run("OneTimeMigration", func(t *testing.T) {
ctx := context.Background()

issuer, _, closeJWKS := issueAccessTokenAndRunJwksServer(t)
defer closeJWKS()

projectID := currentProjectID.Load() + 1
svc := initRPC(t, func(cfg *config.Config) {
cfg.Migrations.OIDCToStytch = []config.OIDCToStytchConfig{
{
ProjectID: projectID,
StytchProjectID: "TEST",
FromIssuer: issuer,
},
}
})
tenant, _ := newTenant(t, svc.Enclave, issuer)
require.NoError(t, svc.Tenants.Add(ctx, tenant))
require.Equal(t, projectID, tenant.ProjectID)
account := newAccount(t, tenant, svc.Enclave, newOIDCIdentity(issuer), nil)
require.NoError(t, svc.Accounts.Put(ctx, account))

// Add more accounts
for i := 0; i < 10; i++ {
acc := newAccount(t, tenant, svc.Enclave, newOIDCIdentity(issuer, fmt.Sprintf("acc-%d", i)), nil)
require.NoError(t, svc.Accounts.Put(ctx, acc))
}

srv := httptest.NewServer(svc.Handler())
defer srv.Close()

c := proto.NewWaasAuthenticatorAdminClient(srv.URL, http.DefaultClient)
header := make(http.Header)
header.Set("Authorization", "Bearer "+adminJWT)
ctx, err := proto.WithHTTPRequestHeaders(context.Background(), header)
require.NoError(t, err)

_, items, err := c.NextMigrationBatch(ctx, proto.Migration_OIDCToStytch, tenant.ProjectID, nil)
require.NoError(t, err)
require.Len(t, items, 11)

itemLogs, itemErrors, err := c.ProcessMigrationBatch(ctx, proto.Migration_OIDCToStytch, tenant.ProjectID, items)
require.NoError(t, err)
assert.Empty(t, itemLogs)
assert.Empty(t, itemErrors)

// There should be now 2 accounts of the original user: original + stytch
resultAccounts, err := svc.Accounts.ListByUserID(ctx, account.UserID)
require.NoError(t, err)
require.Len(t, resultAccounts, 2)

identities := make([]proto.Identity, len(resultAccounts))
for i, acc := range resultAccounts {
identities[i] = proto.Identity(acc.Identity)
}
assert.Contains(t, identities, newStytchIdentity("TEST"))

// 2 accounts of original user + 10 doubled (migrated) additional users
allAccounts, _, err := svc.Accounts.ListByProjectAndIdentity(ctx, data.Page{}, tenant.ProjectID, proto.IdentityType_None, "")
require.NoError(t, err)
require.Len(t, allAccounts, 22)
})
}

0 comments on commit 5ecc38b

Please sign in to comment.