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

Challenge-based 2-step auth with native email #46

Merged
merged 13 commits into from
Jun 6, 2024
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
21 changes: 18 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type Config struct {
Admin AdminConfig `toml:"admin"`
Endpoints EndpointsConfig `toml:"endpoints"`
KMS KMSConfig `toml:"kms"`
SES SESConfig `toml:"ses"`
Builder BuilderConfig `toml:"builder"`
Database DatabaseConfig `toml:"database"`
Telemetry telemetry.Config `toml:"telemetry"`
Tracing TracingConfig `toml:"tracing"`
Expand Down Expand Up @@ -44,10 +46,23 @@ type KMSConfig struct {
DefaultSessionKeys []string `toml:"default_session_keys"`
}

type SESConfig struct {
Region string `toml:"region"`
Source string `toml:"source"`
SourceARN string `toml:"source_arn"`
AccessRoleARN string `toml:"access_role_arn"`
}

type DatabaseConfig struct {
TenantsTable string `toml:"tenants_table"`
AccountsTable string `toml:"accounts_table"`
SessionsTable string `toml:"sessions_table"`
TenantsTable string `toml:"tenants_table"`
AccountsTable string `toml:"accounts_table"`
SessionsTable string `toml:"sessions_table"`
VerificationContextsTable string `toml:"verification_contexts_table"`
}

type BuilderConfig struct {
BaseURL string `toml:"base_url"`
SecretID string `toml:"secret_id"`
}

type TracingConfig struct {
Expand Down
170 changes: 170 additions & 0 deletions data/verification_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package data

import (
"context"
"fmt"
"strconv"
"strings"
"time"

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

type AuthID struct {
ProjectID uint64
IdentityType intents.IdentityType
Verifier string
}

func (id AuthID) String() string {
return fmt.Sprintf("%d/%s/%s", id.ProjectID, id.IdentityType, id.Verifier)
}

func (id *AuthID) FromString(s string) error {
parts := strings.SplitN(s, "/", 3)
if len(parts) != 3 {
return fmt.Errorf("invalid auth session ID format: %s", s)
}

projID, err := strconv.Atoi(parts[0])
if err != nil {
return fmt.Errorf("invalid project ID: %s", s)
}

id.ProjectID = uint64(projID)
id.IdentityType = intents.IdentityType(parts[1])
id.Verifier = parts[2]
return nil
}

func (id *AuthID) MarshalDynamoDBAttributeValue() (types.AttributeValue, error) {
return &types.AttributeValueMemberS{Value: id.String()}, nil
}

func (id *AuthID) UnmarshalDynamoDBAttributeValue(value types.AttributeValue) error {
v, ok := value.(*types.AttributeValueMemberS)
if !ok {
return fmt.Errorf("invalid auth session ID of type: %T", value)
}
return id.FromString(v.Value)
}

type VerificationContext struct {
ID AuthID `dynamodbav:"ID"`
EncryptedKey []byte `dynamodbav:"EncryptedKey"`
Algorithm string `dynamodbav:"Algorithm"`
Ciphertext []byte `dynamodbav:"Ciphertext"`
CreatedAt time.Time `dynamodbav:"CreatedAt"`
}

func (s *VerificationContext) Key() map[string]types.AttributeValue {
return map[string]types.AttributeValue{
"ID": &types.AttributeValueMemberS{Value: s.ID.String()},
}
}

func (s *VerificationContext) CorrespondsTo(data *proto.VerificationContext) bool {
if string(s.ID.IdentityType) != string(data.IdentityType) {
return false
}
if s.ID.Verifier != data.Verifier {
return false
}
if s.ID.ProjectID != data.ProjectID {
return false
}
return true
}

type VerificationContextTable struct {
db DB
tableARN string
}

func NewVerificationContextTable(db DB, tableARN string) *VerificationContextTable {
return &VerificationContextTable{
db: db,
tableARN: tableARN,
}
}

// Put updates a VerificationContext by ID or creates one if it doesn't exist yet.
func (t *VerificationContextTable) Put(ctx context.Context, verifCtx *VerificationContext) error {
verifCtx.CreatedAt = time.Now()

av, err := attributevalue.MarshalMap(verifCtx)
if err != nil {
return fmt.Errorf("marshal input: %w", err)
}
input := &dynamodb.PutItemInput{
TableName: aws.String(t.tableARN),
Item: av,
}
if _, err := t.db.PutItem(ctx, input); err != nil {
return fmt.Errorf("PutItem: %w", err)
}
return nil
}

// Get returns an AuthSession from the DB with the given ID.
//
// AuthSession not being found is not considered an error. Instead, it returns `false` as second value if the AuthSession
// was not found.
func (t *VerificationContextTable) Get(ctx context.Context, id AuthID) (*VerificationContext, bool, error) {
verifCtx := VerificationContext{ID: id}
input := &dynamodb.GetItemInput{
TableName: aws.String(t.tableARN),
Key: verifCtx.Key(),
}

out, err := t.db.GetItem(ctx, input)
if err != nil {
return nil, false, fmt.Errorf("GetItem: %w", err)
}
if len(out.Item) == 0 {
return nil, false, nil
}

if err := attributevalue.UnmarshalMap(out.Item, &verifCtx); err != nil {
return nil, false, fmt.Errorf("unmarshal result: %w", err)
}
return &verifCtx, true, nil
}

func (t *VerificationContextTable) UpdateData(
ctx context.Context, current *VerificationContext, encryptedKey []byte, algorithm string, ciphertext []byte,
) error {
oldEncryptedKey := current.EncryptedKey
oldAlgorithm := current.Algorithm
oldCiphertext := current.Ciphertext

current.EncryptedKey = encryptedKey
current.Algorithm = algorithm
current.Ciphertext = ciphertext

av, err := attributevalue.MarshalMap(current)
if err != nil {
return fmt.Errorf("marshal input: %w", err)
}
input := &dynamodb.PutItemInput{
TableName: aws.String(t.tableARN),
Item: av,
ConditionExpression: aws.String(
"attribute_exists(ID) AND EncryptedKey = :encrypted_key AND Algorithm = :algorithm AND Ciphertext = :ciphertext",
),
ExpressionAttributeValues: map[string]types.AttributeValue{
":encrypted_key": &types.AttributeValueMemberB{Value: oldEncryptedKey},
":algorithm": &types.AttributeValueMemberS{Value: oldAlgorithm},
":ciphertext": &types.AttributeValueMemberB{Value: oldCiphertext},
},
}
if _, err := t.db.PutItem(ctx, input); err != nil {
return fmt.Errorf("UpdateData: %w", err)
}
return nil
}
13 changes: 13 additions & 0 deletions docker/awslocal_ready_hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ fi
awslocal kms create-key --region us-east-1 --tags '[{"TagKey":"_custom_id_","TagValue":"aeb99e0f-9e89-44de-a084-e1817af47778"}]'
awslocal kms create-key --region us-east-1 --tags '[{"TagKey":"_custom_id_","TagValue":"27ebbde0-49d2-4cb6-ad78-4f2c24fe7b79"}]'

awslocal ses verify-email-identity --email [email protected]

awslocal secretsmanager create-secret \
--region us-east-1 \
--name BuilderJWT \
--secret-string 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXJ2aWNlIjoiV2FhUyJ9.-FAkEOb0jtHhoHv6r4O7U8PGOw_b60M9MnSYN9Bm_7A'

awslocal dynamodb create-table \
--region us-east-1 \
--table-name TenantsTable \
Expand All @@ -34,3 +41,9 @@ awslocal dynamodb create-table \
"IndexName=UserID-Index,KeySchema=[{AttributeName=UserID,KeyType=HASH},{AttributeName=Identity,KeyType=SORT}],Projection={ProjectionType=ALL},ProvisionedThroughput={ReadCapacityUnits=10,WriteCapacityUnits=10}" \
"IndexName=Email-Index,KeySchema=[{AttributeName=ProjectScopedEmail,KeyType=HASH},{AttributeName=Identity,KeyType=SORT}],Projection={ProjectionType=ALL},ProvisionedThroughput={ReadCapacityUnits=10,WriteCapacityUnits=10}"

awslocal dynamodb create-table \
--region us-east-1 \
--table-name VerificationContextsTable \
--attribute-definitions AttributeName=ID,AttributeType=S \
--key-schema AttributeName=ID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=10
11 changes: 11 additions & 0 deletions etc/waas-auth.dev.conf
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ QwIDAQAB
tenants_table = "dev_tenants"
accounts_table = "dev_accounts"
sessions_table = "dev_sessions"
verification_contexts_table = "dev_verification_contexts"

[kms]
tenant_keys = ["arn:aws:kms:ca-central-1:767397863481:key/eea4d73b-f055-4941-9fc9-8ed3cff162be"]
default_session_keys = ["arn:aws:kms:ca-central-1:767397863481:key/eea4d73b-f055-4941-9fc9-8ed3cff162be"]

[ses]
region = "ca-central-1"
source = "[email protected]"
source_arn = "arn:aws:ses:ca-central-1:471112647196:identity/sequence.app"
access_role_arn = "arn:aws:iam::471112647196:role/dev-mailer-c797a23"

[builder]
base_url = "https://dev-api.sequence.build"
secret_id = "dev-builder-jwt"
8 changes: 8 additions & 0 deletions etc/waas-auth.sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,16 @@ QwIDAQAB
[database]
tenants_table = "TenantsTable"
sessions_table = "SessionsTable"
verification_contexts_table = "VerificationContextsTable"

[kms]
tenant_keys = ["arn:aws:kms:us-east-1:000000000000:key/27ebbde0-49d2-4cb6-ad78-4f2c24fe7b79"]
default_session_keys = ["arn:aws:kms:us-east-1:000000000000:key/27ebbde0-49d2-4cb6-ad78-4f2c24fe7b79"]
default_transport_keys = ["arn:aws:kms:us-east-1:000000000000:key/aeb99e0f-9e89-44de-a084-e1817af47778"]

[ses]
source = "[email protected]"

[builder]
base_url = "http://host.docker.internal:9999"
secret_id = "BuilderJWT"
20 changes: 11 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ module github.com/0xsequence/waas-authenticator
go 1.22.1

require (
github.com/0xsequence/ethkit v1.24.12
github.com/0xsequence/go-sequence v0.32.1
github.com/0xsequence/ethkit v1.25.0
github.com/0xsequence/go-sequence v0.33.0
github.com/0xsequence/nitrocontrol v0.3.0
github.com/BurntSushi/toml v1.3.2
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2 v1.27.0
github.com/aws/aws-sdk-go-v2/config v1.27.4
github.com/aws/aws-sdk-go-v2/credentials v1.17.4
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.13.6
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.31.1
github.com/aws/aws-sdk-go-v2/service/kms v1.29.1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.29.1
github.com/aws/aws-sdk-go-v2/service/ses v1.22.9
github.com/aws/aws-sdk-go-v2/service/sts v1.28.1
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/httplog v0.3.2
github.com/go-chi/jwtauth/v5 v5.3.0
Expand All @@ -26,7 +30,7 @@ require (
github.com/riandyrn/otelchi v0.7.0
github.com/rs/zerolog v1.32.0
github.com/stretchr/testify v1.9.0
github.com/webrpc/webrpc v0.18.6
github.com/webrpc/webrpc v0.18.7
go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.50.0
go.opentelemetry.io/contrib/propagators/aws v1.25.0
go.opentelemetry.io/otel v1.26.0
Expand All @@ -43,10 +47,9 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.4 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
Expand All @@ -55,7 +58,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.1 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/btcsuite/btcd v0.24.0 // indirect
Expand Down Expand Up @@ -120,7 +122,7 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/sergi/go-diff v1.3.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/spf13/cast v1.6.0 // indirect
Expand Down
Loading
Loading