Skip to content
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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ group :test do
gem 'rspec', '~> 3.2'
gem 'rack-test'
gem 'simplecov'
gem 'vcr'
gem 'webmock'
end
63 changes: 53 additions & 10 deletions lib/omniauth/strategies/line.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,29 @@ class Line < OmniAuth::Strategies::OAuth2
option :name, 'line'
option :scope, 'profile openid'

# omniauth-oauth2 only forwards options named here to the authorize
# URL; anything else set on the strategy is silently dropped.
option :authorize_options, %i[
scope state prompt nonce max_age ui_locales bot_prompt
initial_amr_display switch_amr disable_auto_login
disable_ios_auto_login
]

# The verify call runs inside the user-facing callback request, so a
# slow LINE API must fail fast rather than hold the login hostage.
DEFAULT_VERIFY_TIMEOUTS = { open_timeout: 5, read_timeout: 10 }.freeze
option :verify_options, DEFAULT_VERIFY_TIMEOUTS.dup

# Authorization lives on access.line.me while every API call
# (token exchange, profile, verify) lives on api.line.me.
# LINE documents only client_secret_post for the token endpoint; the
# oauth2 gem's basic-auth default happens to work but is undocumented
# LINE behavior.
option :client_options, {
site: 'https://api.line.me',
authorize_url: 'https://access.line.me/oauth2/v2.1/authorize',
token_url: '/oauth2/v2.1/token'
token_url: '/oauth2/v2.1/token',
auth_scheme: :request_body
}

uid { raw_info['userId'] }
Expand All @@ -36,17 +53,25 @@ class Line < OmniAuth::Strategies::OAuth2

# LINE returns the email claim only inside the ID token (email scope),
# never from the profile endpoint; the verify endpoint decodes it.
# Email is best-effort: any verify failure degrades to nil instead of
# failing the whole login.
def email
id_token = access_token['id_token']
return nil unless id_token

params = {
id_token: id_token,
client_id: client.id
}

response = Net::HTTP.post_form(URI('https://api.line.me/oauth2/v2.1/verify'), params)
JSON.parse(response.body)['email']
response = verify_id_token(id_token)
payload = JSON.parse(response.body)
unless response.is_a?(Net::HTTPSuccess)
log :warn, "ID token verify failed (HTTP #{response.code}): #{payload['error_description'] || payload['error']}"
return nil
end
payload['email']
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ETIMEDOUT => e
log :warn, "ID token verify timed out (#{e.class})"
nil
rescue JSON::ParserError
log :warn, 'ID token verify returned a non-JSON body'
nil
end

# Require: Access token with PROFILE permission issued.
Expand All @@ -56,12 +81,30 @@ def raw_info
profile['email'] = email
profile
end
rescue ::Errno::ETIMEDOUT
rescue ::OAuth2::TimeoutError, ::Errno::ETIMEDOUT
# omniauth-oauth2's callback_phase only maps ::Timeout::Error and
# ::Errno::ETIMEDOUT to fail!(:timeout); OAuth2::TimeoutError is a
# Faraday subclass it would let escape as a 500.
raise ::Timeout::Error
end

def callback_url
full_host + script_name + callback_path
options[:redirect_uri] || (full_host + script_name + callback_path)
end

private

def verify_id_token(id_token)
uri = URI('https://api.line.me/oauth2/v2.1/verify')
request = Net::HTTP::Post.new(uri)
request.set_form_data(id_token: id_token, client_id: client.id)
timeouts = options.verify_options || {}
Net::HTTP.start(uri.host, uri.port,
use_ssl: true,
open_timeout: timeouts[:open_timeout] || DEFAULT_VERIFY_TIMEOUTS[:open_timeout],
read_timeout: timeouts[:read_timeout] || DEFAULT_VERIFY_TIMEOUTS[:read_timeout]) do |http|
http.request(request)
end
end
end
end
Expand Down
53 changes: 53 additions & 0 deletions spec/cassettes/profile_invalid_access_token.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions spec/cassettes/token_invalid_client.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions spec/cassettes/verify_invalid_id_token.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading