Skip to content

Commit

Permalink
introducing Client::Connect as a top-level entry point for producing
Browse files Browse the repository at this point in the history
a Session.
  • Loading branch information
apotonick committed Feb 29, 2024
1 parent bc9b5f2 commit 0f12262
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 36 deletions.
5 changes: 3 additions & 2 deletions lib/trailblazer/pro.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ module Pro

require_relative "pro/trace/decision"
require_relative "pro/session"
require_relative "pro/trace/signin"
require_relative "pro/trace/refresh"
require_relative "pro/client/signin"
require_relative "pro/client/refresh"
require_relative "pro/client"
require_relative "pro/trace/store"
require_relative "pro/trace/wtf"
require_relative "pro/debugger"
Expand Down
51 changes: 51 additions & 0 deletions lib/trailblazer/pro/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module Trailblazer
module Pro
# Provides a valid JWT to authenticate against the PRO API.
#
# We always require an input {:session} and output a {:session}.
# Internally, {Signin} and {Refresh} don't know about session, only kwargs.
module Client
module_function
# session_initialized? --> <> --> valid? --> <> -----------------------------------> (o)
# | | --> Refresh --V ^
# | --> Signin ---------------------> rebuild_session --> |
#
class Connect < Trailblazer::Activity::Railway
step :session_signedin?,
Output(:failure) => Path(track_color: :signin, connect_to: Track(:rebuild)) do # FIXME: move to after {valid?}
# Signin only consumes {:api_key} and friends and doesn't know about {:session}.
step Subprocess(Signin),
In() => :session_to_args#,
# Out() => Trace::Signin::SESSION_VARIABLE_NAMES
end

step Client.method(:valid?), In() => :session_to_args, Inject() => [:now],
Output(:failure) => Path(track_color: :refresh, connect_to: Track(:rebuild)) do
step Subprocess(Refresh), In() => :session_to_args
end

step :rebuild_session, magnetic_to: :rebuild # TODO: assert that success/failure go to right Track.

def session_signedin?(ctx, session:, **)
session.is_a?(Session)
end

def rebuild_session(ctx, session:, **)
session_params = ctx.to_h.slice(*Signin::SESSION_VARIABLE_NAMES)

session = Session.new(
**session.to_h, # old data
**session_params, # new input
)

ctx[:session] = session
ctx[:session_updated] = true
end

def session_to_args(ctx, session:, **)
session.to_h
end
end
end # Client
end
end
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
module Trailblazer::Pro
module Trace
module Client
class Refresh < Trailblazer::Activity::Railway
step :refresh_id_token
step Trace.method(:parse_response)
step Client.method(:parse_response)
step :extract_id_token
step :extract_refresh_token
step Trace.method(:parse_jwt_token)
step Trace.method(:parse_expires_at)
step Client.method(:parse_jwt_token)
step Client.method(:parse_expires_at)

def refresh_id_token(ctx, http: Faraday, refresh_token:, firebase_refresh_url:, **)
ctx[:response] = http.post(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
require "jwt"
require "date"

module Trailblazer::Pro
module Trace
def self.parse_response(ctx, response:, **)
module Client
module_function

def parse_response(ctx, response:, **)
ctx[:parsed_response] = JSON.parse(response.body)
end

require "jwt"
require "date"
def self.parse_jwt_token(ctx, id_token:, **)
def parse_jwt_token(ctx, id_token:, **)
token, _ = JWT.decode(id_token, nil, false, algorithm: "RS256")

ctx[:jwt_token_exp] = token["exp"]
# ctx[:jwt_token] = token
end

def self.parse_expires_at(ctx, jwt_token_exp:, **)
def parse_expires_at(ctx, jwt_token_exp:, **)
ctx[:expires_at] = parse_exp(jwt_token_exp)
end

def self.parse_exp(exp)
def parse_exp(exp)
DateTime.strptime(exp.to_s, "%s")
end

def self.valid?(ctx, now:, expires_at:, **)
def valid?(ctx, now:, expires_at:, **)
# FIXME
puts "id_token expires at #{expires_at}, that is in #{((expires_at - now) * 24 * 60 * 60).to_i} seconds"

Expand All @@ -30,15 +33,15 @@ def self.valid?(ctx, now:, expires_at:, **)

class Signin < Trailblazer::Activity::Railway
step :request_custom_token
step Trace.method(:parse_response)
step Client.method(:parse_response)
step :extract_custom_token
step :extract_data_for_firebase
step :request_id_token
step Trace.method(:parse_response), id: :parse_firebase_response
step Client.method(:parse_response), id: :parse_firebase_response
step :extract_id_token
step :extract_refresh_token
step Trace.method(:parse_jwt_token)
step Trace.method(:parse_expires_at)
step Client.method(:parse_jwt_token)
step Client.method(:parse_expires_at)
# left ->(ctx, response:, **) { puts response.status } # FIXME: better error handling!

PRO_SIGNIN_PATH = "/api/v1/signin_with_api_key"
Expand All @@ -49,7 +52,7 @@ class Signin < Trailblazer::Activity::Railway
:id_token, :refresh_token, :expires_at, :jwt_token_exp, :firebase_signin_url, :firebase_refresh_url, :firebase_upload_url, :firestore_fields_template
]

def request_custom_token(ctx, http: Faraday, api_key:, trailblazer_pro_host: "https://pro.trailblazer.to", **) # DISCUSS: do we like the defaulting?
def request_custom_token(ctx, http: Faraday, api_key:, trailblazer_pro_host:, **)
ctx[:response] = http.post(
"#{trailblazer_pro_host}#{PRO_SIGNIN_PATH}",
{
Expand Down
22 changes: 22 additions & 0 deletions test/client_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require "test_helper"

class ClientTest < Minitest::Spec
let(:session_static_options) do
{
api_key: api_key,
trailblazer_pro_host: trailblazer_pro_host,
firebase_upload_url: "https://firestore.googleapis.com/v1/projects/trb-pro-dev/databases/(default)/documents/traces",
firestore_fields_template: {"version"=>{"stringValue"=>"1"}, "uid"=>{"stringValue"=>"8KwCOTLeK3QdmgtVNUPwr0ukJJc2"}},
firebase_refresh_url: "https://securetoken.googleapis.com/v1/token?key=AIzaSyDVZOdUrI6wOji774hGU0yY_cQw9OAVwzs",
}
end # FIXME: do we need this?


it "Client.() maintains a valid session/JWT for us" do
initial_session = Trailblazer::Pro::Session::Uninitialized.new(trailblazer_pro_host: trailblazer_pro_host, api_key: api_key)

signal, (ctx, _) = Trailblazer::Developer.wtf?(Trailblazer::Pro::Client::Connect, [{session: initial_session}, {}])

assert_session ctx[:session], **session_static_options
end
end
19 changes: 16 additions & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ def assert_equal(asserted, expected)
super(expected, asserted)
end

let(:api_key) { "tpka_f5c698e2_d1ac_48fa_b59f_70e9ab100604" }
# let(:trailblazer_pro_host) { "http://localhost:3000" }
let(:trailblazer_pro_host) { "https://test-pro-rails-jwt.onrender.com" }
let(:api_key) { "tpka_909ae987_c834_43e4_9869_2eefd2aa9bcf" }
let(:trailblazer_pro_host) { "https://testbackend-pro.trb.to" }

after do
Trailblazer::Pro::Session.session = nil
Expand All @@ -37,6 +36,20 @@ def assert_cli_trace(output, operation: Create)
`-- End.success
)
end

def assert_session(session, old_id_token: "", **session_static_options)
session_hash = session.to_h

assert_equal session_hash.slice(:firebase_upload_url, :firestore_fields_template, :firebase_refresh_url, :api_key, :trailblazer_pro_host).sort,
session_static_options.sort
assert_equal session_hash[:refresh_token].size, 183
assert_equal session_hash[:id_token].size, 1054
# assert_equal session_hash[:token].valid?(now: DateTime.now), true # {:token} is {IdToken} instance
# refute_equal session_hash[:id_token], old_id_token
assert_equal Trailblazer::Pro::Client.valid?({}, expires_at: session[:expires_at], now: DateTime.now), true

session_hash
end
end

require "trailblazer/operation"
Expand Down
14 changes: 0 additions & 14 deletions test/wtf_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,4 @@ def model(ctx, **)
assert_equal debugger_url, "https://ide.trailblazer.to/#{trace_id}"
assert_equal Trailblazer::Pro::Session.session, session # session got stored globally
end

def assert_session(session, old_id_token: "", **session_static_options)
session_hash = session.to_h

assert_equal session_hash.slice(:firebase_upload_url, :firestore_fields_template, :firebase_refresh_url, :api_key, :trailblazer_pro_host),
session_static_options
assert_equal session_hash[:refresh_token].size, 183
assert_equal session_hash[:id_token].size, 1054
# assert_equal session_hash[:token].valid?(now: DateTime.now), true # {:token} is {IdToken} instance
# refute_equal session_hash[:id_token], old_id_token
assert_equal Trailblazer::Pro::Trace.valid?({}, expires_at: session[:expires_at], now: DateTime.now), true

session_hash
end
end

0 comments on commit 0f12262

Please sign in to comment.