Skip to content

Commit

Permalink
Add in omniauth.
Browse files Browse the repository at this point in the history
  • Loading branch information
wparad committed May 26, 2023
1 parent 39d01fe commit f320acc
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Change Log for Authress Ruby SDK:

## 2.0
* Update minimum required ruby version to 3.0
* Update minimum required ruby version to 3.0
* Support Omniauth login
32 changes: 32 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ PATH
specs:
authress-sdk (0.0.0.0)
json (~> 2.1, >= 2.1.0)
jwt
oauth2
omniauth-oauth2
typhoeus (>= 1.4)

GEM
Expand All @@ -14,9 +17,30 @@ GEM
diff-lcs (1.5.0)
ethon (0.16.0)
ffi (>= 1.15.0)
faraday (2.7.5)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.2)
ffi (1.15.5)
hashie (5.0.0)
json (2.6.3)
jwt (2.7.0)
method_source (1.0.0)
multi_xml (0.6.0)
oauth2 (2.0.9)
faraday (>= 0.17.3, < 3.0)
jwt (>= 1.0, < 3.0)
multi_xml (~> 0.5)
rack (>= 1.2, < 4)
snaky_hash (~> 2.0)
version_gem (~> 1.1)
omniauth (2.1.1)
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
parallel (1.22.1)
parser (3.2.1.1)
ast (~> 2.4.1)
Expand All @@ -26,6 +50,9 @@ GEM
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
rack (3.0.7)
rack-protection (3.0.6)
rack
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.7.0)
Expand Down Expand Up @@ -56,9 +83,14 @@ GEM
rubocop-ast (1.28.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
unicode-display_width (2.4.2)
version_gem (1.1.2)

PLATFORMS
x86_64-linux
Expand Down
3 changes: 3 additions & 0 deletions authress-sdk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ Gem::Specification.new do |s|

s.add_runtime_dependency 'typhoeus', '>= 1.4'
s.add_runtime_dependency 'json', '~> 2.1', '>= 2.1.0'
s.add_runtime_dependency 'omniauth-oauth2'
s.add_runtime_dependency 'jwt'
s.add_runtime_dependency 'oauth2'

s.add_development_dependency 'rspec'

Expand Down
10 changes: 8 additions & 2 deletions lib/authress-sdk/authress_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ def self.default
@@default ||= AuthressClient.new
end

# Normalize a domain to a URL.
def custom_domain_url
domain_url = URI(@base_url)
domain_url = URI("https://#{domain_url}") if domain_url.scheme.nil?
domain_url.to_s
end

def set_token(token)
@token_provider = ConstantTokenProvider.new(token)
end
Expand Down Expand Up @@ -242,8 +249,7 @@ def sanitize_filename(filename)
def build_request_url(path)
# Add leading and trailing slashes to path
path = "/#{path}".gsub(/\/+/, '/')
puts self.base_url
@base_url + path
custom_domain_url + path
end

# Return Accept header based on an array of accepts provided.
Expand Down
200 changes: 200 additions & 0 deletions lib/authress-sdk/omniauth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# frozen_string_literal: true

require 'json'
require 'oauth2'
require 'omniauth-oauth2'
require_relative './token_validator'
require_relative './authress_client.rb'

include OAuth2

module OmniAuth
module Authress
VERSION = '1.1.0'
end

module Strategies
class Authress < OmniAuth::Strategies::OAuth2
attr_accessor :authress_client
attr_accessor :token_response

def initialize(*args)
super
@authress_client = AuthressSdk::AuthressClient.default
end

option :name, 'authress'
option :pkce, true
option :application_id, nil

# Setup client URLs used during authentication and then call the default
def client
options.client_id = options.application_id
options.client_options.headers = {
'User-Agent' => 'Ruby OmniAuth'
}
options.client_options.auth_scheme = :request_body
options.client_options.site = @authress_client.custom_domain_url
options.client_options.authorize_url = @authress_client.custom_domain_url
options.client_options.token_url = @authress_client.custom_domain_url + '/api/authentication/-/tokens'
# https://github.com/omniauth/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L47
super
end

# Use the "sub" key of the userinfo returned
# as the uid (globally unique string identifier).
uid { user_info['sub'] }

# Build the API credentials hash with returned auth data.
credentials do
if @token_response == nil
return nil
end

hash = {
'token' => @token_response['access_token'],
'id_token' => @token_response['id_token'],
'token_type' => @token_response['token_type'] || 'Bearer',
'expires' => true,
'expires_at' => @token_response['expires_at']
}

# Retrieve and remove authorization params from the session
session_authorize_params = session['authorize_params'] || {}
session.delete('authorize_params')

hash
end

# Store all raw information for use in the session.
extra do
{
raw_info: user_info
}
end

# Build a hash of information about the user
# with keys taken from the Auth Hash Schema.
info do
{
name: user_info['name'] || user_info['sub'],
nickname: user_info['nickname'],
email: user_info['email'],
image: user_info['picture']
}
end

# Define the parameters used for the /authorize endpoint
def authorize_params
params = super
%w[responseLocation flowType].each do |key|
params[key] = request.params[key] if request.params.key?(key)
end

# Generate nonce
params[:nonce] = SecureRandom.hex
# Generate leeway if none exists
params[:leeway] = 60 unless params[:leeway]

params[:responseLocation] = 'query'
params[:flowType] = 'code'

# Store authorize params in the session for token verification
session['authorize_params'] = params.to_hash

params
end

# Declarative override for the request phase of authentication
def request_phase
if no_application_id?
# Do we have a application_id for this Application?
fail!(:missing_application_id)
elsif no_domain?
# Do we have a domain for this Application?
fail!(:missing_domain)
else
# All checks pass, run the Oauth2 request_phase method.
super
end
end

# https://github.com/omniauth/omniauth/blob/master/lib/omniauth/strategy.rb#L416
def callback_phase
begin
error = request.params["error_reason"] || request.params["error"]
if !options.provider_ignores_state && (request.params["state"].to_s.empty? || request.params["state"] != session.delete("omniauth.state"))
fail!(:csrf_detected, CallbackError.new(:csrf_detected, "CSRF detected"))
elsif error
fail!(error, CallbackError.new(request.params["error"], request.params["error_description"] || request.params["error_reason"], request.params["error_uri"]))
else
params = {
'grant_type' => 'authorization_code',
'code' => request.params["code"],
'client_id' => options.application_id,
'redirect_uri' => callback_url
# https://github.com/omniauth/omniauth-oauth2/blob/master/lib/omniauth/strategies/oauth2.rb#L80
}.merge(token_params.to_hash(:symbolize_keys => true))

params_dup = params.dup
params.each_key do |key|
params_dup[key.to_s] = params_dup.delete(key) if key.is_a?(Symbol)
end

@token_response = complete_token_request(params_dup)

env['omniauth.auth'] = auth_hash
call_app!
end
rescue AuthressSdk::TokenValidationError => e
fail!(:token_validation_error, e)
rescue ::OAuth2::Error, CallbackError => e
fail!(:invalid_credentials, e)
rescue ::Timeout::Error, ::Errno::ETIMEDOUT => e
fail!(:timeout, e)
rescue ::SocketError => e
fail!(:failed_to_connect, e)
end
end

def complete_token_request(params, &block)
request_opts = {
raise_errors: options[:raise_errors]
}
request_opts[:body] = params.to_json
request_opts[:headers] = options.client_options.headers
response = client.request(:post, options.client_options.token_url, request_opts, &block)
@access_token = OAuth2::AccessToken.from_hash(client, JSON.parse(response.body)).tap do |access_token|
access_token.response = response if access_token.respond_to?(:response=)
end
return JSON.parse(response.body)
end

# Parse the raw user info.
def user_info
if @token_response && @token_response['id_token']
jwt_payload = @token_response['id_token'] && @token_response['id_token'].to_s && @token_response['id_token'].to_s.split('.')[1]
if jwt_payload
jwt_payload += '=' * (4 - jwt_payload.length.modulo(4))
user_identity = JSON.parse(Base64.decode64(jwt_payload.tr('-_','+/')))
return user_identity
end
end

return nil
end

# Check if the options include a application_id
def no_application_id?
['', nil].include?(options.application_id)
end

# Check if the options include a domain
def no_domain?
['', nil].include?(@authress_client.custom_domain_url)
end
end
end
end

OmniAuth.config.add_camelization 'authress', 'Authress'
13 changes: 13 additions & 0 deletions lib/authress-sdk/token_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'base64'
require 'uri'
require 'json'

module AuthressSdk
class TokenValidationError < StandardError
attr_reader :error_reason
def initialize(msg)
@error_reason = msg
super(msg)
end
end
end

0 comments on commit f320acc

Please sign in to comment.