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

OIDC to Stytch migration #63

Merged
merged 11 commits into from
Aug 22, 2024
25 changes: 13 additions & 12 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,19 @@ import (
)

type Config struct {
Mode Mode `toml:"-"`
Region string `toml:"region"`
Service ServiceConfig `toml:"service"`
Admin AdminConfig `toml:"admin"`
Endpoints EndpointsConfig `toml:"endpoints"`
KMS KMSConfig `toml:"kms"`
SES SESConfig `toml:"ses"`
Builder BuilderConfig `toml:"builder"`
Database DatabaseConfig `toml:"database"`
Signing SigningConfig `toml:"signing"`
Telemetry telemetry.Config `toml:"telemetry"`
Tracing TracingConfig `toml:"tracing"`
Mode Mode `toml:"-"`
Region string `toml:"region"`
Service ServiceConfig `toml:"service"`
Admin AdminConfig `toml:"admin"`
Endpoints EndpointsConfig `toml:"endpoints"`
KMS KMSConfig `toml:"kms"`
SES SESConfig `toml:"ses"`
Builder BuilderConfig `toml:"builder"`
Database DatabaseConfig `toml:"database"`
Signing SigningConfig `toml:"signing"`
Telemetry telemetry.Config `toml:"telemetry"`
Tracing TracingConfig `toml:"tracing"`
Migrations MigrationsConfig `toml:"migrations"`
}

type AdminConfig struct {
Expand Down
11 changes: 11 additions & 0 deletions config/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package config

type MigrationsConfig struct {
OIDCToStytch []OIDCToStytchConfig `toml:"oidc_to_stytch"`
}

type OIDCToStytchConfig struct {
SequenceProject uint64 `toml:"sequence_project"`
StytchProject string `toml:"stytch_project"`
FromIssuer string `toml:"from_issuer"`
}
102 changes: 102 additions & 0 deletions data/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@ func NewAccountTable(db DB, tableARN string, indices AccountIndices) *AccountTab
}
}

// Create creates a new Account or fails if it already exists.
func (t *AccountTable) Create(ctx context.Context, acct *Account) error {
acct.CreatedAt = time.Now()

av, err := attributevalue.MarshalMap(acct)
if err != nil {
return fmt.Errorf("marshal input: %w", err)
}
input := &dynamodb.PutItemInput{
TableName: &t.tableARN,
Item: av,
ConditionExpression: aws.String("attribute_not_exists(#I)"),
ExpressionAttributeNames: map[string]string{
"#I": "Identity",
},
}
if _, err := t.db.PutItem(ctx, input); err != nil {
return fmt.Errorf("PutItem: %w", err)
}
return nil
}

// Put updates an Account by ProjectID or creates one if it doesn't exist yet.
func (t *AccountTable) Put(ctx context.Context, acct *Account) error {
acct.CreatedAt = time.Now()
Expand Down Expand Up @@ -175,3 +197,83 @@ func (t *AccountTable) Delete(ctx context.Context, projectID uint64, identity pr
}
return nil
}

func (t *AccountTable) ListByProjectAndIdentity(ctx context.Context, page Page, projectID uint64, identityType proto.IdentityType, issuer string) ([]*Account, Page, error) {
if page.Limit <= 0 {
patrislav marked this conversation as resolved.
Show resolved Hide resolved
page.Limit = 25
}
if page.Limit > 100 {
page.Limit = 100
}

input := &dynamodb.QueryInput{
TableName: &t.tableARN,
KeyConditionExpression: aws.String("#P = :projectID"),
ExpressionAttributeNames: map[string]string{
"#P": "ProjectID",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":projectID": &types.AttributeValueMemberN{Value: fmt.Sprintf("%d", projectID)},
},
Limit: &page.Limit,
ExclusiveStartKey: page.NextKey,
}

var identCond string
if identityType != proto.IdentityType_None {
identCond = string(identityType) + ":"
if issuer != "" {
identCond += issuer + "#"
}

*input.KeyConditionExpression += " and begins_with(#I, :identCond)"
input.ExpressionAttributeNames["#I"] = "Identity"
input.ExpressionAttributeValues[":identCond"] = &types.AttributeValueMemberS{Value: identCond}
}

out, err := t.db.Query(ctx, input)
if err != nil {
return nil, page, fmt.Errorf("Query: %w", err)
}

accounts := make([]*Account, len(out.Items))
for i, item := range out.Items {
if err := attributevalue.UnmarshalMap(item, &accounts[i]); err != nil {
return nil, page, fmt.Errorf("unmarshal result: %w", err)
}
}

page.NextKey = out.LastEvaluatedKey
return accounts, page, nil
}

func (t *AccountTable) GetBatch(ctx context.Context, projectID uint64, identities []proto.Identity) ([]*Account, error) {
keys := make([]map[string]types.AttributeValue, len(identities))
for i, identity := range identities {
acct := Account{ProjectID: projectID, Identity: Identity(identity)}
keys[i] = acct.Key()
}

input := &dynamodb.BatchGetItemInput{
RequestItems: map[string]types.KeysAndAttributes{
t.tableARN: {Keys: keys},
},
}

out, err := t.db.BatchGetItem(ctx, input)
if err != nil {
return nil, fmt.Errorf("BatchGetItem: %w", err)
}

for _, results := range out.Responses {
accounts := make([]*Account, len(results))
for i, item := range results {
if err := attributevalue.UnmarshalMap(item, &accounts[i]); err != nil {
return nil, fmt.Errorf("unmarshal result: %w", err)
}
}
return accounts, nil
}

return make([]*Account, 0), nil
}
1 change: 1 addition & 0 deletions data/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
// DB is an abstraction over *dynamodb.Client defining the methods that we need for DynamoDB access
type DB interface {
GetItem(ctx context.Context, params *dynamodb.GetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.GetItemOutput, error)
BatchGetItem(ctx context.Context, params *dynamodb.BatchGetItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.BatchGetItemOutput, error)
PutItem(ctx context.Context, params *dynamodb.PutItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.PutItemOutput, error)
DeleteItem(ctx context.Context, params *dynamodb.DeleteItemInput, optFns ...func(*dynamodb.Options)) (*dynamodb.DeleteItemOutput, error)
Query(ctx context.Context, params *dynamodb.QueryInput, optFns ...func(*dynamodb.Options)) (*dynamodb.QueryOutput, error)
Expand Down
61 changes: 61 additions & 0 deletions data/page.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package data

import (
"encoding/base64"
"encoding/json"

"github.com/0xsequence/waas-authenticator/proto"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)

type Page struct {
NextKey map[string]types.AttributeValue
Limit int32
}

func PageFromProto(protoPage *proto.Page) (Page, error) {
page := Page{
Limit: 25,
}

if protoPage != nil {
if protoPage.Limit > 0 {
page.Limit = int32(protoPage.Limit)
}

if protoPage.After != "" {
nextKeyString, err := base64.StdEncoding.DecodeString(protoPage.After)
if err != nil {
return page, err
}
var nextKeyMap map[string]any
if err := json.Unmarshal(nextKeyString, &nextKeyMap); err != nil {
return page, err
}
avMap, err := attributevalue.MarshalMap(nextKeyMap)
if err != nil {
return page, err
}
page.NextKey = avMap
}
}

return page, nil
}

func (p *Page) ToProto() (*proto.Page, error) {
protoPage := &proto.Page{Limit: uint32(p.Limit)}
if p.NextKey != nil {
nextKeyMap := make(map[string]any, len(p.NextKey))
if err := attributevalue.UnmarshalMap(p.NextKey, nextKeyMap); err != nil {
return nil, err
}
b, err := json.Marshal(nextKeyMap)
if err != nil {
return nil, err
}
protoPage.After = base64.StdEncoding.EncodeToString(b)
}
return protoPage, nil
}
5 changes: 5 additions & 0 deletions etc/waas-auth.dev.conf
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@ QwIDAQAB
[signing]
issuer = "https://dev-waas.sequence.app"
audience_prefix = "https://dev.sequence.build/project/"

[[migrations.oidc_to_stytch]]
sequence_project = 694
stytch_project = "project-test-c6241c64-de15-412a-a843-09966c98de57"
from_issuer = "https://oidc-wrapper.sequence.info"
Loading
Loading