Skip to content

Commit

Permalink
wip(sep-10): introduce challenge builder and validation
Browse files Browse the repository at this point in the history
  • Loading branch information
charlie-wasp committed Dec 26, 2022
1 parent 44feb0b commit 808be6e
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 55 deletions.
113 changes: 60 additions & 53 deletions ecosystem/lib/stellar/sep10/challenge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,77 +2,84 @@ module Stellar
module Ecosystem
module SEP10
class Challenge
def initialize(server:, client:, domain: nil, timeout: 300, **options)
@server = server
@client = client
@timeout = timeout
@domain = domain
@options = options

@tx = build_tx
def self.build(server:, client:, domain: nil, timeout: 300, **options)
tx = ChallengeTxBuilder.build(
server: server,
client: client,
domain: domain,
timeout: timeout,
options
)

new(tx: tx)
end

def self.read_xdr(xdr)
envelope = Stellar::TransactionEnvelope.from_xdr(xdr, "base64")
new(tx: envelope.tx)
end

def to_xdr
tx.to_envelope(server).to_xdr(:base64)
end

def validate!(server_keypair:, **options)
validate_tx!(server_keypair: server_keypair)
validate_operations!(options)
end

private

attr_reader :server, :client, :timeout, :domain, :options, :tx
attr_reader :tx

def build_tx
tb = Stellar::TransactionBuilder.new(
source_account: server,
sequence_number: 0,
time_bounds: time_bounds
)
def validate_tx!(server_keypair:)
if tx.seq_num != 0
raise InvalidSep10ChallengeError, "The transaction sequence number should be zero"
end

tb.add_operation(main_operation)
tb.add_operation(auth_domain_operation) if options.key?(:auth_domain)
if tx.source_account != server_keypair.muxed_account
raise InvalidSep10ChallengeError, "The transaction source account is not equal to the server's account"
end

if options[:client_domain].present?
if options[:client_domain_account].blank?
raise "`client_domain_account` is required, if `client_domain` is provided"
end
if tx.operations.size < 1
raise InvalidSep10ChallengeError, "The transaction should contain at least one operation"
end
end

def validate_operations!(options)
auth_op, *rest_ops = tx.operations
client_account_id = auth_op.source_account

tb.add_operation(client_domain_operation)
auth_op_body = auth_op.body.value

if client_account_id.blank?
raise InvalidSep10ChallengeError, "The transaction's operation should contain a source account"
end

tb.build
end
if auth_op.body.arm != :manage_data_op
raise InvalidSep10ChallengeError, "The transaction's first operation should be manageData"
end

def time_bounds
@time_bounds ||= begin
now = Time.now.to_i
Stellar::TimeBounds.new(
min_time: now,
max_time: now + timeout
)
end
end
if options.key?(:domain) && auth_op_body.data_name != "#{options[:domain]} auth"
raise InvalidSep10ChallengeError, "The transaction's operation data name is invalid"
end

def main_operation
Stellar::Operation.manage_data(
name: "#{domain} auth",
value: SecureRandom.base64(48),
source_account: client
)
end
if auth_op_body.data_value.unpack1("m").size != 48
raise InvalidSep10ChallengeError, "The transaction's operation value should be a 64 bytes base64 random string"
end

def auth_domain_operation
Stellar::Operation.manage_data(
name: "web_auth_domain",
value: options[:auth_domain],
source_account: server
)
end
rest_ops.each do |op|
body = op.body
op_params = body.value

def client_domain_operation
Stellar::Operation.manage_data(
name: "client_domain",
value: options[:client_domain],
source_account: options[:client_domain_account]
)
if body.arm != :manage_data_op
raise InvalidSep10ChallengeError, "The transaction has operations that are not of type 'manageData'"
elsif op.source_account != server_keypair.muxed_account && op_params.data_name != "client_domain"
raise InvalidSep10ChallengeError, "The transaction has operations that are unrecognized"
elsif op_params.data_name == "web_auth_domain" && options.key?(:auth_domain) && op_params.data_value != options[:auth_domain]
raise InvalidSep10ChallengeError, "The transaction has 'manageData' operation with 'web_auth_domain' key and invalid value"
end
end
end
end
end
Expand Down
78 changes: 78 additions & 0 deletions ecosystem/lib/stellar/sep10/challenge_tx_builder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Stellar
module Ecosystem
module SEP10
class ChallengeTxBuilder
def self.build(server:, client:, domain: nil, timeout: 300, **options)
new(server: server, client: client, domain: domain, timeout: timeout, options).build
end

def initialize(server:, client:, domain: nil, timeout: 300, **options)
@server = server
@client = client
@timeout = timeout
@domain = domain
@options = options
end

def build
tb = Stellar::TransactionBuilder.new(
source_account: server,
sequence_number: 0,
time_bounds: time_bounds
)

tb.add_operation(main_operation)
tb.add_operation(auth_domain_operation) if options.key?(:auth_domain)

if options[:client_domain].present?
if options[:client_domain_account].blank?
raise "`client_domain_account` is required, if `client_domain` is provided"
end

tb.add_operation(client_domain_operation)
end

tb.build
end

private

attr_reader :server, :client, :timeout, :domain, :options, :tx

def time_bounds
@time_bounds ||= begin
now = Time.now.to_i
Stellar::TimeBounds.new(
min_time: now,
max_time: now + timeout
)
end
end

def main_operation
Stellar::Operation.manage_data(
name: "#{domain} auth",
value: SecureRandom.base64(48),
source_account: client
)
end

def auth_domain_operation
Stellar::Operation.manage_data(
name: "web_auth_domain",
value: options[:auth_domain],
source_account: server
)
end

def client_domain_operation
Stellar::Operation.manage_data(
name: "client_domain",
value: options[:client_domain],
source_account: options[:client_domain_account]
)
end
end
end
end
end
8 changes: 6 additions & 2 deletions ecosystem/lib/stellar/sep10/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ module Stellar
module Ecosystem
module SEP10
class Server
def self.build_challenge_tx(server:, client:, domain: nil, timeout: 300, **options)
def initialize(keypair:)
@keypair = keypair
end

def build_challenge(client:, domain: nil, timeout: 300, **options)
if domain.blank? && options.key?(:anchor_name)
ActiveSupport::Deprecation.new("next release", "stellar-sdk").warn <<~MSG
SEP-10 v2.0.0 requires usage of service home domain instead of anchor name in the challenge transaction.
Expand All @@ -14,7 +18,7 @@ def self.build_challenge_tx(server:, client:, domain: nil, timeout: 300, **optio
end

Challenge.new(
server: server,
server: @keypair,
client: client,
domain: domain,
timeout: timeout,
Expand Down

0 comments on commit 808be6e

Please sign in to comment.