rockauth 0.0.1.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/app/admin/authentication.rb +37 -0
- data/app/admin/provider_authentications.rb +24 -0
- data/app/admin/resource_owner.rb +79 -0
- data/app/controllers/rockauth/authentications_controller.rb +48 -0
- data/app/controllers/rockauth/me_controller.rb +93 -0
- data/app/controllers/rockauth/provider_authentications_controller.rb +72 -0
- data/app/helpers/rockauth/application_helper.rb +4 -0
- data/app/models/rockauth/authentication.rb +6 -0
- data/app/models/rockauth/provider_authentication.rb +9 -0
- data/app/models/rockauth/user.rb +10 -0
- data/app/serializers/rockauth/authentication_serializer.rb +24 -0
- data/app/serializers/rockauth/base_serializer.rb +6 -0
- data/app/serializers/rockauth/error_serializer.rb +5 -0
- data/app/serializers/rockauth/provider_authentication_serializer.rb +5 -0
- data/app/serializers/rockauth/user_serializer.rb +23 -0
- data/app/views/layouts/rockauth/application.html.erb +14 -0
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20150709065335_create_rockauth_users.rb +16 -0
- data/db/migrate/20150709071113_create_rockauth_provider_authentications.rb +16 -0
- data/db/migrate/20150709084233_create_rockauth_authentications.rb +23 -0
- data/lib/generators/rockauth/client_generator.rb +33 -0
- data/lib/generators/rockauth/install_generator.rb +59 -0
- data/lib/generators/rockauth/migrations_generator.rb +9 -0
- data/lib/generators/rockauth/models_generator.rb +11 -0
- data/lib/generators/templates/authentication.rb +4 -0
- data/lib/generators/templates/provider_authentication.rb +5 -0
- data/lib/generators/templates/rockauth_clients.yml +9 -0
- data/lib/generators/templates/rockauth_full_initializer.rb +41 -0
- data/lib/generators/templates/rockauth_providers.json +50 -0
- data/lib/generators/templates/user.rb +7 -0
- data/lib/rockauth.rb +15 -0
- data/lib/rockauth/authenticator.rb +51 -0
- data/lib/rockauth/authenticator/response.rb +32 -0
- data/lib/rockauth/client.rb +4 -0
- data/lib/rockauth/configuration.rb +51 -0
- data/lib/rockauth/controllers.rb +5 -0
- data/lib/rockauth/controllers/authentication.rb +36 -0
- data/lib/rockauth/engine.rb +15 -0
- data/lib/rockauth/errors.rb +18 -0
- data/lib/rockauth/models.rb +9 -0
- data/lib/rockauth/models/authentication.rb +151 -0
- data/lib/rockauth/models/provider_authentication.rb +59 -0
- data/lib/rockauth/models/provider_validation.rb +61 -0
- data/lib/rockauth/models/resource_owner.rb +31 -0
- data/lib/rockauth/models/user.rb +25 -0
- data/lib/rockauth/provider_user_information.rb +103 -0
- data/lib/rockauth/version.rb +3 -0
- data/lib/tasks/rockauth_tasks.rake +9 -0
- metadata +361 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Rockauth
|
2
|
+
class Authenticator::Response < Struct.new(:success, :resource_owner, :authentication)
|
3
|
+
alias_method :success?, :success
|
4
|
+
|
5
|
+
def apply
|
6
|
+
self.success = authentication.save
|
7
|
+
self.resource_owner = authentication.resource_owner # owner is set before_validation
|
8
|
+
end
|
9
|
+
|
10
|
+
def error
|
11
|
+
unless success
|
12
|
+
@error ||= Errors::ControllerError.new 400, I18n.t("rockauth.errors.authentication_failed"), authentication.try(:errors).as_json
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
if success?
|
18
|
+
render_success
|
19
|
+
else
|
20
|
+
render_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def render_success
|
25
|
+
{ json: authentication, status: 200 }
|
26
|
+
end
|
27
|
+
|
28
|
+
def render_error
|
29
|
+
Rockauth::Configuration.error_renderer.call(error)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rockauth
|
2
|
+
Configuration = Struct.new(*%i(allowed_password_length email_regexp token_time_to_live clients
|
3
|
+
resource_owner_class warn_missing_social_auth_gems providers jwt
|
4
|
+
serializers generate_active_admin_resources active_admin_menu_name error_renderer)) do
|
5
|
+
def resource_owner_class= arg
|
6
|
+
@constantized_resource_owner_class = nil
|
7
|
+
@resource_owner_class = arg
|
8
|
+
end
|
9
|
+
def resource_owner_class
|
10
|
+
@constantized_resource_owner_class ||= (@resource_owner_class.respond_to?(:constantize) ? @resource_owner_class.constantize : @resource_owner_class)
|
11
|
+
end
|
12
|
+
end.new.tap do |config|
|
13
|
+
config.allowed_password_length = 8..72
|
14
|
+
config.email_regexp = /\A[^@\s]+@([^@\s]+\.)+[^@\W]+\z/
|
15
|
+
config.token_time_to_live = 365 * 24 * 60 * 60
|
16
|
+
config.clients = []
|
17
|
+
config.resource_owner_class = 'Rockauth::User'
|
18
|
+
config.warn_missing_social_auth_gems = true
|
19
|
+
|
20
|
+
config.providers = Struct.new(*%i(twitter instagram google_plus)).new.tap do |providers|
|
21
|
+
%i(twitter instagram google_plus).each do |provider|
|
22
|
+
providers.public_send("#{provider}=", {})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
config.jwt = Struct.new(*%i(secret issuer signing_method)).new.tap do |jwt_config|
|
27
|
+
jwt_config.secret = ''
|
28
|
+
jwt_config.issuer = ''
|
29
|
+
jwt_config.signing_method = 'HS256'
|
30
|
+
end
|
31
|
+
|
32
|
+
config.serializers = Struct.new(*%i(error user authentication provider_authentication)).new.tap do |serializers|
|
33
|
+
serializers.error = "Rockauth::ErrorSerializer"
|
34
|
+
serializers.user = "Rockauth::UserSerializer"
|
35
|
+
serializers.authentication = "Rockauth::AuthenticationSerializer"
|
36
|
+
serializers.provider_authentication = "Rockauth::ProviderAuthenticationSerializer"
|
37
|
+
end
|
38
|
+
|
39
|
+
config.generate_active_admin_resources = nil
|
40
|
+
config.active_admin_menu_name = 'Authentication'
|
41
|
+
|
42
|
+
config.error_renderer = -> error do
|
43
|
+
{ json: error, serializer: Rockauth::Configuration.serializers.error.safe_constantize, status: error.status_code }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.configure
|
48
|
+
yield Configuration if block_given?
|
49
|
+
Configuration
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rockauth
|
2
|
+
module Controllers::Authentication
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def render_error status_code=500, message=I18n.t("rockauth.errors.server_error"), validation_errors=nil
|
6
|
+
error = Errors::ControllerError.new(status_code, message, validation_errors)
|
7
|
+
render Rockauth::Configuration.error_renderer.call(error)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render_unauthorized
|
11
|
+
render_error 401, I18n.t("rockauth.errors.unauthorized")
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate_resource_owner!
|
15
|
+
render_unauthorized unless current_authentication.try(:active?)
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_resource_owner
|
19
|
+
@current_resource_owner ||= current_authentication.try(:resource_owner)
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_authentication
|
23
|
+
if @_authentication_checked
|
24
|
+
@current_authentication
|
25
|
+
else
|
26
|
+
@_authentication_checked = true
|
27
|
+
@current_authentication = Authenticator.verified_authentication_for_request request, self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
included do
|
31
|
+
include ActionController::Helpers # TODO: we dont want to force apis to have helpers if we can avoid it.....
|
32
|
+
helper_method :current_resource_owner
|
33
|
+
helper_method :current_authentication
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails-api'
|
2
|
+
|
3
|
+
module Rockauth
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
initializer "rockauth.inject.controller_concern" do
|
6
|
+
ActionController::API.send :include, Rockauth::Controllers::Authentication
|
7
|
+
end
|
8
|
+
|
9
|
+
initializer "rockauth.load_active_admin_resources" do
|
10
|
+
if Rockauth::Configuration.generate_active_admin_resources || Rockauth::Configuration.generate_active_admin_resources.nil? && defined?(ActiveAdmin)
|
11
|
+
ActiveAdmin.application.load_paths += [File.expand_path(File.dirname(__FILE__) + '../../../app/admin')]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Rockauth
|
4
|
+
module Errors
|
5
|
+
ControllerError = Struct.new(:status_code, :message, :validation_errors) do
|
6
|
+
extend ActiveModel::Naming
|
7
|
+
include ActiveModel::Serialization
|
8
|
+
|
9
|
+
def self.model_name
|
10
|
+
'Error'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.active_model_serializer
|
14
|
+
Rockauth::Configuration.serializers.error.safe_constantize
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rockauth
|
2
|
+
module Models
|
3
|
+
autoload :Authentication, 'rockauth/models/authentication'
|
4
|
+
autoload :ProviderAuthentication, 'rockauth/models/provider_authentication'
|
5
|
+
autoload :ProviderValidation, 'rockauth/models/provider_validation'
|
6
|
+
autoload :ResourceOwner, 'rockauth/models/resource_owner'
|
7
|
+
autoload :User, 'rockauth/models/user'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'jwt'
|
2
|
+
|
3
|
+
module Rockauth
|
4
|
+
module Models::Authentication
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def client
|
8
|
+
@client ||= if client_id.present?
|
9
|
+
Configuration.clients.find { |c| c.id == client_id }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def password?
|
14
|
+
auth_type == 'password'
|
15
|
+
end
|
16
|
+
|
17
|
+
def assertion?
|
18
|
+
auth_type == 'assertion'
|
19
|
+
end
|
20
|
+
|
21
|
+
def registration?
|
22
|
+
auth_type == 'registration'
|
23
|
+
end
|
24
|
+
|
25
|
+
def time_to_live= t
|
26
|
+
@time_to_live = t
|
27
|
+
end
|
28
|
+
|
29
|
+
def time_to_live
|
30
|
+
@time_to_live ||= Configuration.token_time_to_live
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_token_id
|
34
|
+
self.token_id ||= SecureRandom.base64(24)
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash_token_id
|
38
|
+
self.hashed_token_id ||= self.class.hash_token_id token_id
|
39
|
+
end
|
40
|
+
|
41
|
+
def active?
|
42
|
+
Time.at(expiration) > Time.now
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_payload? payload
|
46
|
+
active? && payload['iat'] == issued_at && payload['exp'] == expiration
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_token
|
50
|
+
self.token ||= JWT.encode jwt_payload, Configuration.jwt.secret, Configuration.jwt.signing_method
|
51
|
+
end
|
52
|
+
|
53
|
+
def jwt_payload
|
54
|
+
{
|
55
|
+
iss: Configuration.jwt.issuer,
|
56
|
+
iat: issued_at,
|
57
|
+
exp: expiration,
|
58
|
+
aud: client_id,
|
59
|
+
sub: resource_owner_id,
|
60
|
+
jti: token_id
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
def rockauth_authentication include_associations: true, provider_authentication_class_name: "Rockauth::ProviderAuthentication"
|
66
|
+
if include_associations
|
67
|
+
belongs_to :resource_owner, polymorphic: true, inverse_of: :authentications
|
68
|
+
belongs_to :provider_authentication, class_name: provider_authentication_class_name
|
69
|
+
|
70
|
+
accepts_nested_attributes_for :provider_authentication
|
71
|
+
end
|
72
|
+
|
73
|
+
scope :expired, -> { where('expiration <= ?', Time.now.to_i) }
|
74
|
+
scope :unexpired, -> { where('expiration > ?', Time.now.to_i) }
|
75
|
+
|
76
|
+
%i(resource_owner_class password username token_id token client_secret).each do |key|
|
77
|
+
attr_accessor key
|
78
|
+
end
|
79
|
+
|
80
|
+
validates_presence_of :auth_type
|
81
|
+
validates_inclusion_of :auth_type, in: %w(password assertion registration)
|
82
|
+
validates_presence_of :client_id
|
83
|
+
validates_presence_of :client_secret, on: :create
|
84
|
+
validates_presence_of :resource_owner
|
85
|
+
validates_presence_of :expiration
|
86
|
+
validates_presence_of :issued_at
|
87
|
+
validates_presence_of :auth_type
|
88
|
+
|
89
|
+
before_validation on: :create do
|
90
|
+
self.expiration ||= Time.now.to_i + time_to_live
|
91
|
+
self.issued_at ||= Time.now.to_i
|
92
|
+
|
93
|
+
if password?
|
94
|
+
self.resource_owner = resource_owner_class.with_username(username).first
|
95
|
+
elsif assertion?
|
96
|
+
build_provider_authentication unless self.provider_authentication.present?
|
97
|
+
provider_authentication.authentication = self
|
98
|
+
self.provider_authentication = provider_authentication.exchange
|
99
|
+
self.resource_owner = provider_authentication.resource_owner
|
100
|
+
end
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
validates_presence_of :username, if: :password?, on: :create
|
106
|
+
validates_presence_of :password, if: :password?, on: :create
|
107
|
+
validate on: :create, if: :password? do
|
108
|
+
if resource_owner.present? && !resource_owner.authenticate(password)
|
109
|
+
errors.add :password, :invalid
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
validates_presence_of :provider_authentication, if: :assertion?, on: :create
|
114
|
+
|
115
|
+
validate on: :create do
|
116
|
+
if client.blank?
|
117
|
+
errors.add :client_id, :invalid
|
118
|
+
elsif client.secret != client_secret
|
119
|
+
errors.add :client_secret, :invalid
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
before_create do
|
124
|
+
generate_token_id
|
125
|
+
generate_token
|
126
|
+
hash_token_id
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
class << self
|
131
|
+
def for_token token
|
132
|
+
begin
|
133
|
+
payload = JWT.decode(token, Configuration.jwt.secret).first
|
134
|
+
authentication = where(hashed_token_id: hash_token_id(payload['jti'])).first
|
135
|
+
authentication if authentication.present? && authentication.valid_payload?(payload)
|
136
|
+
rescue JWT::VerificationError => e
|
137
|
+
Rails.logger.error "[Rockauth] Possible Forgery Attempt: #{e}"
|
138
|
+
nil
|
139
|
+
rescue JWT::DecodeError
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def hash_token_id jti
|
145
|
+
Digest::SHA2.hexdigest jti
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rockauth
|
2
|
+
module Models::ProviderAuthentication
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def provider_authentication include_associations: true, authentication_class_name: 'Rockauth::Authentication'
|
7
|
+
|
8
|
+
if include_associations
|
9
|
+
belongs_to :resource_owner, polymorphic: true, inverse_of: :provider_authentications
|
10
|
+
has_many :authentications, class_name: authentication_class_name, dependent: :nullify
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :authentication
|
14
|
+
|
15
|
+
validates_presence_of :resource_owner
|
16
|
+
validates_uniqueness_of :provider_user_id, scope: :provider
|
17
|
+
validates_presence_of :provider
|
18
|
+
validates_presence_of :provider_user_id
|
19
|
+
validates_presence_of :provider_access_token
|
20
|
+
|
21
|
+
validate :validate_attributes_unchangable
|
22
|
+
|
23
|
+
delegate :resource_owner_class, to: :authentication
|
24
|
+
|
25
|
+
define_method :validate_attributes_unchangable do
|
26
|
+
%i(resource_owner_id resource_owner_type provider provider_user_id).each do |key|
|
27
|
+
errors.add key, :rockauth_cannot_be_changed if !new_record? && public_send(:"#{key}_changed?")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method :exchange do
|
32
|
+
result = self
|
33
|
+
|
34
|
+
configure_from_provider
|
35
|
+
|
36
|
+
if provider_user_id.present? && provider.present?
|
37
|
+
result = self.class.where(provider: provider, provider_user_id: provider_user_id).first
|
38
|
+
if result.present?
|
39
|
+
result.provider_user_information = provider_user_information
|
40
|
+
result.assign_attributes_from_provider
|
41
|
+
result
|
42
|
+
else
|
43
|
+
handle_missing_resource_owner_on_valid_assertion
|
44
|
+
result = self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
result
|
49
|
+
end
|
50
|
+
|
51
|
+
define_method :handle_missing_resource_owner_on_valid_assertion do
|
52
|
+
self.resource_owner = resource_owner_class.new
|
53
|
+
resource_owner.assign_attributes_from_provider_user(provider_user_information)
|
54
|
+
resource_owner.provider_authentications << self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Rockauth
|
2
|
+
module Models::ProviderValidation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
attr_accessor :provider_user_information
|
5
|
+
|
6
|
+
included do
|
7
|
+
validates_inclusion_of :provider, in: ->(instance) { instance.class.valid_networks }
|
8
|
+
attr_accessor :skip_provider_authentication
|
9
|
+
before_validation :configure_from_provider, unless: :skip_provider_authentication
|
10
|
+
|
11
|
+
def configure_from_provider
|
12
|
+
if provider.present? && self.class.valid_networks.include?(provider)
|
13
|
+
self.provider_user_information = instance_exec &self.class.network_validator(provider)
|
14
|
+
assign_attributes_from_provider
|
15
|
+
end
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def assign_attributes_from_provider
|
21
|
+
return unless provider_user_information.present?
|
22
|
+
self.provider_user_id = provider_user_information.user_id
|
23
|
+
end
|
24
|
+
|
25
|
+
{ facebook: 'fb_graph2', instagram: 'instagram', twitter: 'twitter', google_plus: nil }.each do |key, value|
|
26
|
+
begin
|
27
|
+
require value if value.present?
|
28
|
+
provider_network key do
|
29
|
+
ProviderUserInformation.for_provider(provider, provider_access_token, provider_access_token_secret)
|
30
|
+
end
|
31
|
+
rescue LoadError
|
32
|
+
if Rockauth::Configuration.warn_missing_social_auth_gems
|
33
|
+
warn "Rockauth: Could not load the #{value} gem, #{key.to_s.humanize} provider authentication disabled"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
def inherited subclass
|
42
|
+
super
|
43
|
+
subclass.instance_variable_set :@network_validator_configuration, (@network_validator_configuration || {}).dup
|
44
|
+
end
|
45
|
+
|
46
|
+
def provider_network provider, &block
|
47
|
+
fail ArgumentError, "no block given" unless block_given?
|
48
|
+
@network_validator_configuration ||= {}
|
49
|
+
@network_validator_configuration[provider.to_s] = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_networks
|
53
|
+
@network_validator_configuration.keys
|
54
|
+
end
|
55
|
+
|
56
|
+
def network_validator provider
|
57
|
+
@network_validator_configuration[provider.to_s]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|