Class: SpreeCmCommissioner::UserAuthenticator

Inherits:
Object
  • Object
show all
Defined in:
app/services/spree_cm_commissioner/user_authenticator.rb

Constant Summary collapse

FLOW_TYPE_DETECTORS =

A separate params key from id_token (social login) means we only ever attempt to decode a token that's actually claiming to be a load-test token, instead of decoding every request's id_token to find out.

{
  'load_test_auth' => -> (params) { load_test_id_token?(params[:user_id_token]) },
  'cross_app_handoff_auth' => -> (params) { params[:handoff_token].present? },
  'login_auth' => -> (params) { params.key?(:username) && params.key?(:password) },
  'telegram_oauth_auth' => -> (params) { params[:telegram_id_token].present? },
  'social_auth' => -> (params) { params.key?(:id_token) },
  'facebook_auth' => -> (params) { params.key?(:fb_access_token) },
  'telegram_web_app_auth' => -> (params) { params.key?(:telegram_init_data) && params.key?(:tg_bot) },
  'vattanac_bank_web_app_auth' => -> (params) { params.key?(:session_id) }
}.freeze

Class Method Summary collapse

Class Method Details

.auth_context(params) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 34

def self.auth_context(params)
  oauth_application = find_oauth_application(params)
  tenant_id = oauth_application&.tenant_id

  case flow_type(params)
  when 'load_test_auth'
    # k6 sends the load-test JWT as `user_id_token`; internally it's a login_token (see
    # UserPasswordAuthenticator's self-identifying, token-only path).
    options = { login_token: params[:user_id_token], tenant_id: tenant_id }
    SpreeCmCommissioner::UserPasswordAuthenticator.call(options)
  when 'cross_app_handoff_auth'
    options = { handoff_token: params[:handoff_token], tenant_id: tenant_id }
    SpreeCmCommissioner::UserCrossAppHandoffAuthenticator.call(options)
  when 'login_auth'
    options = { login: params[:username], password: params[:password], tenant_id: tenant_id }
    SpreeCmCommissioner::UserPasswordAuthenticator.call(options)
  when 'telegram_oauth_auth'
    options = { id_token: params[:telegram_id_token], tenant_id: tenant_id }
    SpreeCmCommissioner::UserTelegramOauthAuthenticator.call(options)
  when 'social_auth'
    options = { id_token: params[:id_token], tenant_id: tenant_id }
    SpreeCmCommissioner::UserIdTokenAuthenticator.call(options)
  when 'facebook_auth'
    options = { fb_access_token: params[:fb_access_token], tenant_id: tenant_id }
    SpreeCmCommissioner::UserFbTokenAuthenticator.call(options)
  when 'telegram_web_app_auth'
    options = { telegram_init_data: params[:telegram_init_data], telegram_bot_username: params[:tg_bot] }
    SpreeCmCommissioner::UserTelegramWebAppAuthenticator.call(options)
  when 'vattanac_bank_web_app_auth'
    options = { session_id: params[:session_id] }
    SpreeCmCommissioner::UserVattanacBankWebAppAuthenticator.call(options)
  end
end

.call!(params) ⇒ Object

:username, :password :id_token (Firebase social login) :telegram_id_token -> Telegram OIDC login (Firebase/Google social login) :user_id_token (load-test JWT, self-identifying, minted by LoadTest::LoginToken) :handoff_token (cross-app sign-in JWT, self-identifying, minted by CrossAppHandoffTokens::Encode) :fb_access_token :telegram_init_data, :tg_bot :session_id



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 13

def self.call!(params)
  context = auth_context(params)
  raise exception(context.message) unless context.success?

  user = context.user

  # Check if user.tenant_id is nil first to keep our old logic work as usual
  if user.tenant_id.nil? && params[:client_id].blank? && params[:client_secret].blank?
    return user
  elsif params[:client_id].present? && params[:client_secret].present?
    oauth_application = find_oauth_application(params)
    raise exception(I18n.t('authenticator.invalid_client_credentials')) unless oauth_application

    validate_tenant_match!(user, oauth_application)
  else
    raise exception(I18n.t('authenticator.invalid_or_missing_params'))
  end

  user
end

.exception(message) ⇒ Object



93
94
95
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 93

def self.exception(message)
  Doorkeeper::Errors::DoorkeeperError.new(message)
end

.find_oauth_application(params) ⇒ Object



103
104
105
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 103

def self.find_oauth_application(params)
  Spree::OauthApplication.find_by(uid: params[:client_id], secret: params[:client_secret])
end

.flow_type(params) ⇒ Object



82
83
84
85
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 82

def self.flow_type(params)
  flow, = FLOW_TYPE_DETECTORS.find { |_flow, detector| detector.call(params) }
  flow || raise(exception(I18n.t('authenticator.invalid_or_missing_params')))
end

.load_test_id_token?(token) ⇒ Boolean

True only for a load-test id_token — it's signed with our own secret, so it decodes here whereas a real Firebase/Google id_token (RS256, Google's keys) returns nil.

Returns:

  • (Boolean)


89
90
91
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 89

def self.load_test_id_token?(token)
  token.present? && SpreeCmCommissioner::LoadTest::LoginToken.decode(token).present?
end

.validate_tenant_match!(user, oauth_application) ⇒ Object

Raises:

  • (ActiveRecord::RecordNotFound)


97
98
99
100
101
# File 'app/services/spree_cm_commissioner/user_authenticator.rb', line 97

def self.validate_tenant_match!(user, oauth_application)
  return if user.tenant_id == oauth_application.tenant_id

  raise ActiveRecord::RecordNotFound
end