sorcery 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -2
- data/.rubocop_todo.yml +139 -1
- data/.travis.yml +3 -7
- data/CHANGELOG.md +17 -0
- data/lib/generators/sorcery/templates/initializer.rb +16 -2
- data/lib/sorcery/controller.rb +4 -1
- data/lib/sorcery/controller/submodules/external.rb +2 -1
- data/lib/sorcery/controller/submodules/remember_me.rb +1 -1
- data/lib/sorcery/engine.rb +7 -1
- data/lib/sorcery/model.rb +5 -5
- data/lib/sorcery/providers/discord.rb +52 -0
- data/lib/sorcery/providers/linkedin.rb +35 -10
- data/lib/sorcery/providers/vk.rb +1 -1
- data/lib/sorcery/version.rb +1 -1
- data/sorcery.gemspec +3 -3
- data/spec/controllers/controller_oauth2_spec.rb +15 -6
- data/spec/controllers/controller_remember_me_spec.rb +15 -12
- data/spec/controllers/controller_spec.rb +11 -1
- data/spec/providers/example_provider_spec.rb +17 -0
- data/spec/providers/example_spec.rb +17 -0
- data/spec/rails_app/app/assets/config/manifest.js +1 -0
- data/spec/rails_app/app/controllers/sorcery_controller.rb +28 -0
- data/spec/rails_app/config/routes.rb +4 -0
- data/spec/support/providers/example.rb +11 -0
- data/spec/support/providers/example_provider.rb +11 -0
- metadata +16 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2eacf343e86db96ee2b99e31a647efbdca41cc859dd3aaa5d5300fcf80cb2f6f
|
4
|
+
data.tar.gz: 0ab8e2eb73204b108e52ec39c4622e654cf48fa2ec75159bd5a3686a2855156e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4d4e09af8fd96f8c4a025a50c7d9d539ae584fac256f443a488063a91acffe2e1539a0a89b7aa4323116fdc6b9a6bb4e881072d91113e5f98be697e2296005b
|
7
|
+
data.tar.gz: b61da2da586d75af4f1f4df8046accfc7928428005ba3101e6c59fb30421c27b7e358b8b37f6e702c839a2002987e5b43af8c8de7adfd91e70ee8c749ebabf3d
|
data/.rubocop.yml
CHANGED
@@ -3,7 +3,7 @@ inherit_from: .rubocop_todo.yml
|
|
3
3
|
AllCops:
|
4
4
|
Exclude:
|
5
5
|
- 'lib/generators/sorcery/templates/**/*'
|
6
|
-
TargetRubyVersion: 2.
|
6
|
+
TargetRubyVersion: 2.6
|
7
7
|
|
8
8
|
# See: https://github.com/rubocop-hq/rubocop/issues/3344
|
9
9
|
Style/DoubleNegation:
|
@@ -21,7 +21,7 @@ Metrics/BlockLength:
|
|
21
21
|
Exclude:
|
22
22
|
- 'lib/**/*'
|
23
23
|
- 'spec/**/*'
|
24
|
-
|
24
|
+
Layout/LineLength:
|
25
25
|
Exclude:
|
26
26
|
- 'lib/**/*'
|
27
27
|
- 'spec/**/*'
|
data/.rubocop_todo.yml
CHANGED
@@ -1,7 +1,145 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2019-12-18 16:18:24 -0800 using RuboCop version 0.78.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 1
|
10
|
+
# Configuration parameters: Include.
|
11
|
+
# Include: **/*.gemspec
|
12
|
+
Gemspec/RequiredRubyVersion:
|
13
|
+
Exclude:
|
14
|
+
- 'sorcery.gemspec'
|
15
|
+
|
16
|
+
# Offense count: 1
|
17
|
+
# Cop supports --auto-correct.
|
18
|
+
# Configuration parameters: AllowAdjacentOneLineDefs, NumberOfEmptyLines.
|
19
|
+
Layout/EmptyLineBetweenDefs:
|
20
|
+
Exclude:
|
21
|
+
- 'lib/sorcery/providers/line.rb'
|
22
|
+
|
23
|
+
# Offense count: 83
|
24
|
+
# Cop supports --auto-correct.
|
25
|
+
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
26
|
+
# SupportedHashRocketStyles: key, separator, table
|
27
|
+
# SupportedColonStyles: key, separator, table
|
28
|
+
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
|
29
|
+
Layout/HashAlignment:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
# Offense count: 2
|
33
|
+
# Configuration parameters: AllowSafeAssignment.
|
34
|
+
Lint/AssignmentInCondition:
|
35
|
+
Exclude:
|
36
|
+
- 'spec/rails_app/app/controllers/sorcery_controller.rb'
|
37
|
+
|
38
|
+
# Offense count: 1
|
39
|
+
# Cop supports --auto-correct.
|
40
|
+
Lint/NonDeterministicRequireOrder:
|
41
|
+
Exclude:
|
42
|
+
- 'spec/spec_helper.rb'
|
43
|
+
|
44
|
+
# Offense count: 4
|
45
|
+
# Cop supports --auto-correct.
|
46
|
+
Lint/RedundantCopDisableDirective:
|
47
|
+
Exclude:
|
48
|
+
- 'lib/sorcery/controller.rb'
|
49
|
+
- 'lib/sorcery/model.rb'
|
50
|
+
- 'spec/rails_app/config/application.rb'
|
51
|
+
- 'spec/shared_examples/user_shared_examples.rb'
|
52
|
+
|
53
|
+
# Offense count: 4
|
54
|
+
# Cop supports --auto-correct.
|
55
|
+
Lint/SendWithMixinArgument:
|
56
|
+
Exclude:
|
57
|
+
- 'lib/sorcery.rb'
|
58
|
+
- 'lib/sorcery/engine.rb'
|
59
|
+
- 'lib/sorcery/test_helpers/internal/rails.rb'
|
60
|
+
|
61
|
+
# Offense count: 4
|
62
|
+
# Configuration parameters: AllowComments.
|
63
|
+
Lint/SuppressedException:
|
64
|
+
Exclude:
|
65
|
+
- 'lib/sorcery/controller.rb'
|
66
|
+
- 'lib/sorcery/model.rb'
|
67
|
+
- 'spec/rails_app/config/application.rb'
|
68
|
+
- 'spec/shared_examples/user_shared_examples.rb'
|
69
|
+
|
70
|
+
# Offense count: 2
|
71
|
+
# Cop supports --auto-correct.
|
72
|
+
# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
|
73
|
+
Lint/UnusedBlockArgument:
|
74
|
+
Exclude:
|
75
|
+
- 'spec/shared_examples/user_shared_examples.rb'
|
76
|
+
|
77
|
+
# Offense count: 1
|
78
|
+
# Configuration parameters: EnforcedStyle.
|
79
|
+
# SupportedStyles: annotated, template, unannotated
|
80
|
+
Style/FormatStringToken:
|
81
|
+
Exclude:
|
82
|
+
- 'lib/generators/sorcery/install_generator.rb'
|
83
|
+
|
84
|
+
# Offense count: 121
|
85
|
+
# Cop supports --auto-correct.
|
86
|
+
# Configuration parameters: EnforcedStyle.
|
87
|
+
# SupportedStyles: always, never
|
88
|
+
Style/FrozenStringLiteralComment:
|
89
|
+
Enabled: false
|
90
|
+
|
91
|
+
# Offense count: 3
|
92
|
+
# Configuration parameters: MinBodyLength.
|
93
|
+
Style/GuardClause:
|
94
|
+
Exclude:
|
95
|
+
- 'lib/sorcery/controller/submodules/brute_force_protection.rb'
|
96
|
+
- 'lib/sorcery/controller/submodules/http_basic_auth.rb'
|
97
|
+
- 'lib/sorcery/controller/submodules/remember_me.rb'
|
98
|
+
|
99
|
+
# Offense count: 3
|
100
|
+
# Cop supports --auto-correct.
|
101
|
+
# Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
|
102
|
+
# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
|
103
|
+
Style/HashSyntax:
|
104
|
+
Exclude:
|
105
|
+
- 'lib/sorcery/adapters/active_record_adapter.rb'
|
106
|
+
- 'lib/sorcery/test_helpers/rails/integration.rb'
|
107
|
+
|
108
|
+
# Offense count: 49
|
109
|
+
# Cop supports --auto-correct.
|
110
|
+
Style/IfUnlessModifier:
|
111
|
+
Enabled: false
|
112
|
+
|
113
|
+
# Offense count: 2
|
114
|
+
# Cop supports --auto-correct.
|
115
|
+
Style/RedundantBegin:
|
116
|
+
Exclude:
|
117
|
+
- 'lib/sorcery/controller.rb'
|
118
|
+
- 'lib/sorcery/model.rb'
|
119
|
+
|
120
|
+
# Offense count: 4
|
121
|
+
# Cop supports --auto-correct.
|
122
|
+
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods.
|
123
|
+
# AllowedMethods: present?, blank?, presence, try, try!
|
124
|
+
Style/SafeNavigation:
|
125
|
+
Exclude:
|
126
|
+
- 'lib/sorcery/controller/config.rb'
|
127
|
+
- 'lib/sorcery/controller/submodules/brute_force_protection.rb'
|
128
|
+
- 'lib/sorcery/controller/submodules/remember_me.rb'
|
129
|
+
- 'lib/sorcery/model.rb'
|
130
|
+
|
131
|
+
# Offense count: 7
|
132
|
+
# Cop supports --auto-correct.
|
133
|
+
# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
|
134
|
+
# SupportedStyles: single_quotes, double_quotes
|
135
|
+
Style/StringLiterals:
|
136
|
+
Exclude:
|
137
|
+
- 'spec/controllers/controller_oauth2_spec.rb'
|
138
|
+
- 'spec/sorcery_crypto_providers_spec.rb'
|
139
|
+
|
140
|
+
# Offense count: 2
|
141
|
+
# Cop supports --auto-correct.
|
142
|
+
Style/UnpackFirst:
|
143
|
+
Exclude:
|
144
|
+
- 'lib/sorcery/crypto_providers/aes256.rb'
|
145
|
+
- 'spec/sorcery_crypto_providers_spec.rb'
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
## HEAD
|
3
3
|
|
4
|
+
## 0.15.0
|
5
|
+
|
6
|
+
* Fix brute force vuln due to callbacks no being ran [#235](https://github.com/Sorcery/sorcery/pull/235)
|
7
|
+
* Revert on_load change due to breaking existing applications [#234](https://github.com/Sorcery/sorcery/pull/234)
|
8
|
+
* Add forget_me! and force_forget_me! test cases [#216](https://github.com/Sorcery/sorcery/pull/216)
|
9
|
+
* In `generic_send_email`, check responds_to [#211](https://github.com/Sorcery/sorcery/pull/211)
|
10
|
+
* Fix typo [#219](https://github.com/Sorcery/sorcery/pull/219)
|
11
|
+
* Fix deprecation warnings in Rails 6 [#209](https://github.com/Sorcery/sorcery/pull/209)
|
12
|
+
* Add ruby 2.6.5 to the travis build [#215](https://github.com/Sorcery/sorcery/pull/215)
|
13
|
+
* Add discord provider [#185](https://github.com/Sorcery/sorcery/pull/185)
|
14
|
+
* Remove MySQL database creation call [#214](https://github.com/Sorcery/sorcery/pull/214)
|
15
|
+
* Use id instead of uid for VK provider [#199](https://github.com/Sorcery/sorcery/pull/199)
|
16
|
+
* Don't :return_t JSON requests after login [#197](https://github.com/Sorcery/sorcery/pull/197)
|
17
|
+
* Fix email scope for LinkedIn Provider [#191](https://github.com/Sorcery/sorcery/pull/191)
|
18
|
+
* Ignore cookies when undefined cookies [#187](https://github.com/Sorcery/sorcery/pull/187)
|
19
|
+
* Allow for custom providers with multi-word class names. [#190](https://github.com/Sorcery/sorcery/pull/190)
|
20
|
+
|
4
21
|
## 0.14.0
|
5
22
|
|
6
23
|
* Update LinkedIn to use OAuth 2 [#189](https://github.com/Sorcery/sorcery/pull/189)
|
@@ -88,11 +88,19 @@ Rails.application.config.sorcery.configure do |config|
|
|
88
88
|
#
|
89
89
|
# config.ca_file =
|
90
90
|
|
91
|
+
# Linkedin requires r_emailaddress scope to fetch user's email address.
|
92
|
+
# You can skip including the email field if you use an intermediary signup form. (using build_from method).
|
93
|
+
# The r_emailaddress scope is only necessary if you are using the create_from method directly.
|
94
|
+
#
|
91
95
|
# config.linkedin.key = ""
|
92
96
|
# config.linkedin.secret = ""
|
93
97
|
# config.linkedin.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=linkedin"
|
94
|
-
# config.linkedin.user_info_mapping = {
|
95
|
-
#
|
98
|
+
# config.linkedin.user_info_mapping = {
|
99
|
+
# first_name: 'localizedFirstName',
|
100
|
+
# last_name: 'localizedLastName',
|
101
|
+
# email: 'emailAddress'
|
102
|
+
# }
|
103
|
+
# config.linkedin.scope = "r_liteprofile r_emailaddress"
|
96
104
|
#
|
97
105
|
#
|
98
106
|
# For information about XING API:
|
@@ -215,6 +223,12 @@ Rails.application.config.sorcery.configure do |config|
|
|
215
223
|
# config.line.secret = ""
|
216
224
|
# config.line.callback_url = "http://mydomain.com:3000/oauth/callback?provider=line"
|
217
225
|
|
226
|
+
# For infromation about Discord API
|
227
|
+
# https://discordapp.com/developers/docs/topics/oauth2
|
228
|
+
# config.discord.key = "xxxxxx"
|
229
|
+
# config.discord.secret = "xxxxxx"
|
230
|
+
# config.discord.callback_url = "http://localhost:3000/oauth/callback?provider=discord"
|
231
|
+
# config.discord.scope = "email guilds"
|
218
232
|
# --- user config ---
|
219
233
|
config.user_config do |user|
|
220
234
|
# -- core --
|
data/lib/sorcery/controller.rb
CHANGED
@@ -25,7 +25,10 @@ module Sorcery
|
|
25
25
|
def require_login
|
26
26
|
return if logged_in?
|
27
27
|
|
28
|
-
|
28
|
+
if Config.save_return_to_url && request.get? && !request.xhr? && !request.format.json?
|
29
|
+
session[:return_to_url] = request.url
|
30
|
+
end
|
31
|
+
|
29
32
|
send(Config.not_authenticated_action)
|
30
33
|
end
|
31
34
|
|
@@ -26,6 +26,7 @@ module Sorcery
|
|
26
26
|
require 'sorcery/providers/instagram'
|
27
27
|
require 'sorcery/providers/auth0'
|
28
28
|
require 'sorcery/providers/line'
|
29
|
+
require 'sorcery/providers/discord'
|
29
30
|
|
30
31
|
Config.module_eval do
|
31
32
|
class << self
|
@@ -38,7 +39,7 @@ module Sorcery
|
|
38
39
|
providers.each do |name|
|
39
40
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
40
41
|
def self.#{name}
|
41
|
-
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.
|
42
|
+
@#{name} ||= Sorcery::Providers.const_get('#{name}'.to_s.classify).new
|
42
43
|
end
|
43
44
|
RUBY
|
44
45
|
end
|
@@ -59,7 +59,7 @@ module Sorcery
|
|
59
59
|
# and logs the user in if found.
|
60
60
|
# Runs as a login source. See 'current_user' method for how it is used.
|
61
61
|
def login_from_cookie
|
62
|
-
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token])
|
62
|
+
user = cookies.signed[:remember_me_token] && user_class.sorcery_adapter.find_by_remember_me_token(cookies.signed[:remember_me_token]) if defined? cookies
|
63
63
|
if user && user.has_remember_me_token?
|
64
64
|
set_remember_me_cookie!(user)
|
65
65
|
session[:user_id] = user.id.to_s
|
data/lib/sorcery/engine.rb
CHANGED
@@ -7,12 +7,18 @@ module Sorcery
|
|
7
7
|
class Engine < Rails::Engine
|
8
8
|
config.sorcery = ::Sorcery::Controller::Config
|
9
9
|
|
10
|
+
# TODO: Should this include a modified version of the helper methods?
|
10
11
|
initializer 'extend Controller with sorcery' do
|
11
|
-
#
|
12
|
+
# FIXME: on_load is needed to fix Rails 6 deprecations, but it breaks
|
13
|
+
# applications due to undefined method errors.
|
14
|
+
# ActiveSupport.on_load(:action_controller_api) do
|
12
15
|
if defined?(ActionController::API)
|
13
16
|
ActionController::API.send(:include, Sorcery::Controller)
|
14
17
|
end
|
15
18
|
|
19
|
+
# FIXME: on_load is needed to fix Rails 6 deprecations, but it breaks
|
20
|
+
# applications due to undefined method errors.
|
21
|
+
# ActiveSupport.on_load(:action_controller_base) do
|
16
22
|
if defined?(ActionController::Base)
|
17
23
|
ActionController::Base.send(:include, Sorcery::Controller)
|
18
24
|
ActionController::Base.helper_method :current_user
|
data/lib/sorcery/model.rb
CHANGED
@@ -102,10 +102,6 @@ module Sorcery
|
|
102
102
|
|
103
103
|
set_encryption_attributes
|
104
104
|
|
105
|
-
unless user.valid_password?(credentials[1])
|
106
|
-
return authentication_response(user: user, failure: :invalid_password, &block)
|
107
|
-
end
|
108
|
-
|
109
105
|
if user.respond_to?(:active_for_authentication?) && !user.active_for_authentication?
|
110
106
|
return authentication_response(user: user, failure: :inactive, &block)
|
111
107
|
end
|
@@ -118,6 +114,10 @@ module Sorcery
|
|
118
114
|
end
|
119
115
|
end
|
120
116
|
|
117
|
+
unless user.valid_password?(credentials[1])
|
118
|
+
return authentication_response(user: user, failure: :invalid_password, &block)
|
119
|
+
end
|
120
|
+
|
121
121
|
authentication_response(user: user, return_value: user, &block)
|
122
122
|
end
|
123
123
|
|
@@ -206,7 +206,7 @@ module Sorcery
|
|
206
206
|
def generic_send_email(method, mailer)
|
207
207
|
config = sorcery_config
|
208
208
|
mail = config.send(mailer).send(config.send(method), self)
|
209
|
-
return unless
|
209
|
+
return unless mail.respond_to?(config.email_delivery_method)
|
210
210
|
|
211
211
|
mail.send(config.email_delivery_method)
|
212
212
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Sorcery
|
2
|
+
module Providers
|
3
|
+
# This class adds support for OAuth with discordapp.com
|
4
|
+
|
5
|
+
class Discord < Base
|
6
|
+
include Protocols::Oauth2
|
7
|
+
|
8
|
+
attr_accessor :auth_path, :scope, :token_url, :user_info_path
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
super
|
12
|
+
|
13
|
+
@scope = 'identify'
|
14
|
+
@site = 'https://discordapp.com/'
|
15
|
+
@auth_path = '/api/oauth2/authorize'
|
16
|
+
@token_url = '/api/oauth2/token'
|
17
|
+
@user_info_path = '/api/users/@me'
|
18
|
+
@state = SecureRandom.hex(16)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_user_hash(access_token)
|
22
|
+
response = access_token.get(user_info_path)
|
23
|
+
body = JSON.parse(response.body)
|
24
|
+
auth_hash(access_token).tap do |h|
|
25
|
+
h[:user_info] = body
|
26
|
+
h[:uid] = body['id']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# calculates and returns the url to which the user should be redirected,
|
31
|
+
# to get authenticated at the external provider's site.
|
32
|
+
def login_url(_params, _session)
|
33
|
+
authorize_url(authorize_url: auth_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# tries to login the user from access token
|
37
|
+
def process_callback(params, _session)
|
38
|
+
args = {}.tap do |a|
|
39
|
+
a[:code] = params[:code] if params[:code]
|
40
|
+
end
|
41
|
+
get_access_token(
|
42
|
+
args,
|
43
|
+
token_url: token_url,
|
44
|
+
client_id: @key,
|
45
|
+
client_secret: @secret,
|
46
|
+
grant_type: 'authorization_code',
|
47
|
+
token_method: :post
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -9,25 +9,26 @@ module Sorcery
|
|
9
9
|
class Linkedin < Base
|
10
10
|
include Protocols::Oauth2
|
11
11
|
|
12
|
-
attr_accessor :auth_url, :scope, :token_url, :user_info_url
|
12
|
+
attr_accessor :auth_url, :scope, :token_url, :user_info_url, :email_info_url
|
13
13
|
|
14
14
|
def initialize
|
15
15
|
super
|
16
16
|
|
17
|
-
@site
|
18
|
-
@auth_url
|
19
|
-
@token_url
|
20
|
-
@user_info_url
|
21
|
-
@
|
22
|
-
@
|
17
|
+
@site = 'https://api.linkedin.com'
|
18
|
+
@auth_url = '/oauth/v2/authorization'
|
19
|
+
@token_url = '/oauth/v2/accessToken'
|
20
|
+
@user_info_url = 'https://api.linkedin.com/v2/me'
|
21
|
+
@email_info_url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))'
|
22
|
+
@scope = 'r_liteprofile r_emailaddress'
|
23
|
+
@state = SecureRandom.hex(16)
|
23
24
|
end
|
24
25
|
|
25
26
|
def get_user_hash(access_token)
|
26
|
-
|
27
|
+
user_info = get_user_info(access_token)
|
27
28
|
|
28
29
|
auth_hash(access_token).tap do |h|
|
29
|
-
h[:user_info] =
|
30
|
-
h[:uid]
|
30
|
+
h[:user_info] = user_info
|
31
|
+
h[:uid] = h[:user_info]['id']
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
@@ -45,6 +46,30 @@ module Sorcery
|
|
45
46
|
|
46
47
|
get_access_token(args, token_url: token_url, token_method: :post)
|
47
48
|
end
|
49
|
+
|
50
|
+
def get_user_info(access_token)
|
51
|
+
response = access_token.get(user_info_url)
|
52
|
+
user_info = JSON.parse(response.body)
|
53
|
+
|
54
|
+
if email_in_scope?
|
55
|
+
email = fetch_email(access_token)
|
56
|
+
|
57
|
+
return user_info.merge(email)
|
58
|
+
end
|
59
|
+
|
60
|
+
user_info
|
61
|
+
end
|
62
|
+
|
63
|
+
def email_in_scope?
|
64
|
+
scope.include?('r_emailaddress')
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_email(access_token)
|
68
|
+
email_response = access_token.get(email_info_url)
|
69
|
+
email_info = JSON.parse(email_response.body)['elements'].first
|
70
|
+
|
71
|
+
email_info['handle~']
|
72
|
+
end
|
48
73
|
end
|
49
74
|
end
|
50
75
|
end
|
data/lib/sorcery/providers/vk.rb
CHANGED
@@ -37,7 +37,7 @@ module Sorcery
|
|
37
37
|
user_hash[:user_info] = user_hash[:user_info]['response'][0]
|
38
38
|
user_hash[:user_info]['full_name'] = [user_hash[:user_info]['first_name'], user_hash[:user_info]['last_name']].join(' ')
|
39
39
|
|
40
|
-
user_hash[:uid] = user_hash[:user_info]['
|
40
|
+
user_hash[:uid] = user_hash[:user_info]['id']
|
41
41
|
user_hash[:user_info]['email'] = access_token.params['email']
|
42
42
|
end
|
43
43
|
user_hash
|
data/lib/sorcery/version.rb
CHANGED
data/sorcery.gemspec
CHANGED
@@ -19,20 +19,20 @@ Gem::Specification.new do |s|
|
|
19
19
|
]
|
20
20
|
|
21
21
|
# TODO: Cleanup formatting.
|
22
|
-
# rubocop:disable
|
22
|
+
# rubocop:disable Layout/LineLength
|
23
23
|
s.description = 'Provides common authentication needs such as signing in/out, activating by email and resetting password.'
|
24
24
|
s.summary = 'Magical authentication for Rails applications'
|
25
25
|
s.homepage = 'https://github.com/Sorcery/sorcery'
|
26
26
|
s.post_install_message = "As of version 1.0 oauth/oauth2 won't be automatically bundled so you may need to add those dependencies to your Gemfile.\n"
|
27
27
|
s.post_install_message += 'You may need oauth2 if you use external providers such as any of these: https://github.com/Sorcery/sorcery/tree/master/lib/sorcery/providers'
|
28
|
-
# rubocop:enable
|
28
|
+
# rubocop:enable Layout/LineLength
|
29
29
|
|
30
30
|
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
31
31
|
s.require_paths = ['lib']
|
32
32
|
|
33
33
|
s.licenses = ['MIT']
|
34
34
|
|
35
|
-
s.required_ruby_version = '>= 2.
|
35
|
+
s.required_ruby_version = '>= 2.4.9'
|
36
36
|
|
37
37
|
s.add_dependency 'bcrypt', '~> 3.1'
|
38
38
|
s.add_dependency 'oauth', '~> 0.4', '>= 0.4.4'
|
@@ -155,7 +155,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
155
155
|
expect(flash[:notice]).to eq 'Success!'
|
156
156
|
end
|
157
157
|
|
158
|
-
%i[github google liveid vk salesforce paypal slack wechat microsoft instagram auth0].each do |provider|
|
158
|
+
%i[github google liveid vk salesforce paypal slack wechat microsoft instagram auth0 discord].each do |provider|
|
159
159
|
describe "with #{provider}" do
|
160
160
|
it 'login_at redirects correctly' do
|
161
161
|
get :"login_at_test_#{provider}"
|
@@ -217,6 +217,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
217
217
|
instagram
|
218
218
|
auth0
|
219
219
|
line
|
220
|
+
discord
|
220
221
|
]
|
221
222
|
)
|
222
223
|
|
@@ -261,6 +262,9 @@ describe SorceryController, active_record: true, type: :controller do
|
|
261
262
|
sorcery_controller_external_property_set(:line, :key, "eYVNBjBDi33aa9GkA3w")
|
262
263
|
sorcery_controller_external_property_set(:line, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
263
264
|
sorcery_controller_external_property_set(:line, :callback_url, "http://blabla.com")
|
265
|
+
sorcery_controller_external_property_set(:discord, :key, 'eYVNBjBDi33aa9GkA3w')
|
266
|
+
sorcery_controller_external_property_set(:discord, :secret, 'XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8')
|
267
|
+
sorcery_controller_external_property_set(:discord, :callback_url, 'http://blabla.com')
|
264
268
|
end
|
265
269
|
|
266
270
|
after(:each) do
|
@@ -283,7 +287,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
283
287
|
expect(ActionMailer::Base.deliveries.size).to eq old_size
|
284
288
|
end
|
285
289
|
|
286
|
-
%i[github google liveid vk salesforce paypal wechat microsoft instagram auth0].each do |provider|
|
290
|
+
%i[github google liveid vk salesforce paypal wechat microsoft instagram auth0 discord].each do |provider|
|
287
291
|
it "does not send activation email to external users (#{provider})" do
|
288
292
|
old_size = ActionMailer::Base.deliveries.size
|
289
293
|
create_new_external_user provider
|
@@ -307,7 +311,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
307
311
|
sorcery_reload!(%i[activity_logging external])
|
308
312
|
end
|
309
313
|
|
310
|
-
%w[facebook github google liveid vk salesforce slack].each do |provider|
|
314
|
+
%w[facebook github google liveid vk salesforce slack discord].each do |provider|
|
311
315
|
context "when #{provider}" do
|
312
316
|
before(:each) do
|
313
317
|
sorcery_controller_property_set(:register_login_time, true)
|
@@ -346,7 +350,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
346
350
|
|
347
351
|
let(:user) { double('user', id: 42) }
|
348
352
|
|
349
|
-
%w[facebook github google liveid vk salesforce slack].each do |provider|
|
353
|
+
%w[facebook github google liveid vk salesforce slack discord].each do |provider|
|
350
354
|
context "when #{provider}" do
|
351
355
|
before(:each) do
|
352
356
|
sorcery_model_property_set(:authentications_class, Authentication)
|
@@ -427,7 +431,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
427
431
|
# response for VK auth
|
428
432
|
'response' => [
|
429
433
|
{
|
430
|
-
'
|
434
|
+
'id' => '123',
|
431
435
|
'first_name' => 'Noam',
|
432
436
|
'last_name' => 'Ben Ari'
|
433
437
|
}
|
@@ -479,6 +483,7 @@ describe SorceryController, active_record: true, type: :controller do
|
|
479
483
|
instagram
|
480
484
|
auth0
|
481
485
|
line
|
486
|
+
discord
|
482
487
|
]
|
483
488
|
)
|
484
489
|
sorcery_controller_external_property_set(:facebook, :key, 'eYVNBjBDi33aa9GkA3w')
|
@@ -521,6 +526,9 @@ describe SorceryController, active_record: true, type: :controller do
|
|
521
526
|
sorcery_controller_external_property_set(:line, :key, "eYVNBjBDi33aa9GkA3w")
|
522
527
|
sorcery_controller_external_property_set(:line, :secret, "XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8")
|
523
528
|
sorcery_controller_external_property_set(:line, :callback_url, "http://blabla.com")
|
529
|
+
sorcery_controller_external_property_set(:discord, :key, 'eYVNBjBDi33aa9GkA3w')
|
530
|
+
sorcery_controller_external_property_set(:discord, :secret, 'XpbeSdCoaKSmQGSeokz5qcUATClRW5u08QWNfv71N8')
|
531
|
+
sorcery_controller_external_property_set(:discord, :callback_url, 'http://blabla.com')
|
524
532
|
end
|
525
533
|
|
526
534
|
def provider_url(provider)
|
@@ -535,7 +543,8 @@ describe SorceryController, active_record: true, type: :controller do
|
|
535
543
|
wechat: "https://open.weixin.qq.com/connect/qrconnect?appid=#{::Sorcery::Controller::Config.wechat.key}&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=snsapi_login&state=#wechat_redirect",
|
536
544
|
microsoft: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=#{::Sorcery::Controller::Config.microsoft.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=openid+email+https%3A%2F%2Fgraph.microsoft.com%2FUser.Read&state",
|
537
545
|
instagram: "https://api.instagram.com/oauth/authorize?client_id=#{::Sorcery::Controller::Config.instagram.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=#{::Sorcery::Controller::Config.instagram.scope}&state",
|
538
|
-
auth0: "https://sorcery-test.auth0.com/authorize?client_id=#{::Sorcery::Controller::Config.auth0.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=openid+profile+email&state"
|
546
|
+
auth0: "https://sorcery-test.auth0.com/authorize?client_id=#{::Sorcery::Controller::Config.auth0.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=openid+profile+email&state",
|
547
|
+
discord: "https://discordapp.com/api/oauth2/authorize?client_id=#{::Sorcery::Controller::Config.discord.key}&display&redirect_uri=http%3A%2F%2Fblabla.com&response_type=code&scope=identify&state"
|
539
548
|
}[provider]
|
540
549
|
end
|
541
550
|
end
|
@@ -6,14 +6,19 @@ describe SorceryController, type: :controller do
|
|
6
6
|
# ----------------- REMEMBER ME -----------------------
|
7
7
|
context 'with remember me features' do
|
8
8
|
before(:all) do
|
9
|
+
if SORCERY_ORM == :active_record
|
10
|
+
MigrationHelper.migrate("#{Rails.root}/db/migrate/remember_me")
|
11
|
+
User.reset_column_information
|
12
|
+
end
|
13
|
+
|
9
14
|
sorcery_reload!([:remember_me])
|
10
15
|
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
after(:all) do
|
18
|
+
if SORCERY_ORM == :active_record
|
19
|
+
MigrationHelper.rollback("#{Rails.root}/db/migrate/remember_me")
|
20
|
+
end
|
21
|
+
end
|
17
22
|
|
18
23
|
before(:each) do
|
19
24
|
allow(user).to receive(:remember_me_token)
|
@@ -32,19 +37,17 @@ describe SorceryController, type: :controller do
|
|
32
37
|
end
|
33
38
|
|
34
39
|
it 'clears cookie on forget_me!' do
|
35
|
-
cookies[
|
36
|
-
get :
|
40
|
+
request.cookies[:remember_me_token] = { value: 'asd54234dsfsd43534', expires: 3600 }
|
41
|
+
get :test_logout_with_forget_me
|
37
42
|
|
38
|
-
|
39
|
-
expect(cookies['remember_me_token']).to be_nil
|
43
|
+
expect(response.cookies[:remember_me_token]).to be_nil
|
40
44
|
end
|
41
45
|
|
42
46
|
it 'clears cookie on force_forget_me!' do
|
43
|
-
cookies[
|
47
|
+
request.cookies[:remember_me_token] = { value: 'asd54234dsfsd43534', expires: 3600 }
|
44
48
|
get :test_logout_with_force_forget_me
|
45
49
|
|
46
|
-
|
47
|
-
expect(cookies['remember_me_token']).to be_nil
|
50
|
+
expect(response.cookies[:remember_me_token]).to be_nil
|
48
51
|
end
|
49
52
|
|
50
53
|
it 'login(email,password,remember_me) logs user in and remembers' do
|
@@ -150,6 +150,16 @@ describe SorceryController, type: :controller do
|
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
153
|
+
it 'require_login before_action does not save the url for JSON requests' do
|
154
|
+
get :some_action, format: :json
|
155
|
+
expect(session[:return_to_url]).to be_nil
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'require_login before_action does not save the url for XHR requests' do
|
159
|
+
get :some_action, xhr: true
|
160
|
+
expect(session[:return_to_url]).to be_nil
|
161
|
+
end
|
162
|
+
|
153
163
|
it 'on successful login the user is redirected to the url he originally wanted' do
|
154
164
|
session[:return_to_url] = 'http://test.host/some_action'
|
155
165
|
post :test_return_to, params: { email: 'bla@bla.com', password: 'secret' }
|
@@ -161,7 +171,7 @@ describe SorceryController, type: :controller do
|
|
161
171
|
# --- auto_login(user) ---
|
162
172
|
specify { should respond_to(:auto_login) }
|
163
173
|
|
164
|
-
it 'auto_login(user)
|
174
|
+
it 'auto_login(user) logs in a user instance' do
|
165
175
|
session[:user_id] = nil
|
166
176
|
subject.auto_login(user)
|
167
177
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'sorcery/providers/base'
|
5
|
+
|
6
|
+
describe Sorcery::Providers::ExampleProvider do
|
7
|
+
before(:all) do
|
8
|
+
sorcery_reload!([:external])
|
9
|
+
sorcery_controller_property_set(:external_providers, [:example_provider])
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'fetching a multi-word custom provider' do
|
13
|
+
it 'returns the provider' do
|
14
|
+
expect(Sorcery::Controller::Config.example_provider).to be_a(Sorcery::Providers::ExampleProvider)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'sorcery/providers/base'
|
5
|
+
|
6
|
+
describe Sorcery::Providers::Example do
|
7
|
+
before(:all) do
|
8
|
+
sorcery_reload!([:external])
|
9
|
+
sorcery_controller_property_set(:external_providers, [:example])
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'fetching a single-word custom provider' do
|
13
|
+
it 'returns the provider' do
|
14
|
+
expect(Sorcery::Controller::Config.example).to be_a(Sorcery::Providers::Example)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
{}
|
@@ -6,6 +6,7 @@ class SorceryController < ActionController::Base
|
|
6
6
|
before_action :require_login_from_http_basic, only: [:test_http_basic_auth]
|
7
7
|
before_action :require_login, only: %i[
|
8
8
|
test_logout
|
9
|
+
test_logout_with_forget_me
|
9
10
|
test_logout_with_force_forget_me
|
10
11
|
test_should_be_logged_in
|
11
12
|
some_action
|
@@ -50,6 +51,13 @@ class SorceryController < ActionController::Base
|
|
50
51
|
head :ok
|
51
52
|
end
|
52
53
|
|
54
|
+
def test_logout_with_forget_me
|
55
|
+
remember_me!
|
56
|
+
forget_me!
|
57
|
+
logout
|
58
|
+
head :ok
|
59
|
+
end
|
60
|
+
|
53
61
|
def test_logout_with_force_forget_me
|
54
62
|
remember_me!
|
55
63
|
force_forget_me!
|
@@ -158,6 +166,10 @@ class SorceryController < ActionController::Base
|
|
158
166
|
login_at(:auth0)
|
159
167
|
end
|
160
168
|
|
169
|
+
def login_at_test_discord
|
170
|
+
login_at(:discord)
|
171
|
+
end
|
172
|
+
|
161
173
|
def test_login_from_twitter
|
162
174
|
if (@user = login_from(:twitter))
|
163
175
|
redirect_to 'bla', notice: 'Success!'
|
@@ -280,6 +292,14 @@ class SorceryController < ActionController::Base
|
|
280
292
|
end
|
281
293
|
end
|
282
294
|
|
295
|
+
def test_login_from_discord
|
296
|
+
if (@user = login_from(:discord))
|
297
|
+
redirect_to 'bla', notice: 'Success!'
|
298
|
+
else
|
299
|
+
redirect_to 'blu', alert: 'Failed!'
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
283
303
|
def test_return_to_with_external_twitter
|
284
304
|
if (@user = login_from(:twitter))
|
285
305
|
redirect_back_or_to 'bla', notice: 'Success!'
|
@@ -402,6 +422,14 @@ class SorceryController < ActionController::Base
|
|
402
422
|
end
|
403
423
|
end
|
404
424
|
|
425
|
+
def test_return_to_with_external_discord
|
426
|
+
if (@user = login_from(:discord))
|
427
|
+
redirect_back_or_to 'bla', notice: 'Success!'
|
428
|
+
else
|
429
|
+
redirect_to 'blu', alert: 'Failed!'
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
405
433
|
def test_create_from_provider
|
406
434
|
provider = params[:provider]
|
407
435
|
login_from(provider)
|
@@ -11,6 +11,7 @@ AppRoot::Application.routes.draw do
|
|
11
11
|
get :test_login_from_cookie
|
12
12
|
get :test_login_from
|
13
13
|
get :test_logout_with_remember
|
14
|
+
get :test_logout_with_forget_me
|
14
15
|
get :test_logout_with_force_forget_me
|
15
16
|
get :test_invalidate_active_session
|
16
17
|
get :test_should_be_logged_in
|
@@ -33,6 +34,7 @@ AppRoot::Application.routes.draw do
|
|
33
34
|
get :test_login_from_instagram
|
34
35
|
get :test_login_from_auth0
|
35
36
|
get :test_login_from_line
|
37
|
+
get :test_login_from_discord
|
36
38
|
get :login_at_test
|
37
39
|
get :login_at_test_twitter
|
38
40
|
get :login_at_test_facebook
|
@@ -49,6 +51,7 @@ AppRoot::Application.routes.draw do
|
|
49
51
|
get :login_at_test_instagram
|
50
52
|
get :login_at_test_auth0
|
51
53
|
get :login_at_test_line
|
54
|
+
get :login_at_test_discord
|
52
55
|
get :test_return_to_with_external
|
53
56
|
get :test_return_to_with_external_twitter
|
54
57
|
get :test_return_to_with_external_facebook
|
@@ -65,6 +68,7 @@ AppRoot::Application.routes.draw do
|
|
65
68
|
get :test_return_to_with_external_instagram
|
66
69
|
get :test_return_to_with_external_auth0
|
67
70
|
get :test_return_to_with_external_line
|
71
|
+
get :test_return_to_with_external_discord
|
68
72
|
get :test_http_basic_auth
|
69
73
|
get :some_action_making_a_non_persisted_change_to_the_user
|
70
74
|
post :test_login_with_remember
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorcery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Noam Ben Ari
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2020-05-02 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bcrypt
|
@@ -52,22 +52,22 @@ dependencies:
|
|
52
52
|
name: oauth2
|
53
53
|
requirement: !ruby/object:Gem::Requirement
|
54
54
|
requirements:
|
55
|
-
- - "~>"
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: '1.0'
|
58
55
|
- - ">="
|
59
56
|
- !ruby/object:Gem::Version
|
60
57
|
version: 0.8.0
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.0'
|
61
61
|
type: :runtime
|
62
62
|
prerelease: false
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
64
64
|
requirements:
|
65
|
-
- - "~>"
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version: '1.0'
|
68
65
|
- - ">="
|
69
66
|
- !ruby/object:Gem::Version
|
70
67
|
version: 0.8.0
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '1.0'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: byebug
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -254,6 +254,7 @@ files:
|
|
254
254
|
- lib/sorcery/protocols/oauth2.rb
|
255
255
|
- lib/sorcery/providers/auth0.rb
|
256
256
|
- lib/sorcery/providers/base.rb
|
257
|
+
- lib/sorcery/providers/discord.rb
|
257
258
|
- lib/sorcery/providers/facebook.rb
|
258
259
|
- lib/sorcery/providers/github.rb
|
259
260
|
- lib/sorcery/providers/google.rb
|
@@ -295,10 +296,13 @@ files:
|
|
295
296
|
- spec/controllers/controller_session_timeout_spec.rb
|
296
297
|
- spec/controllers/controller_spec.rb
|
297
298
|
- spec/orm/active_record.rb
|
299
|
+
- spec/providers/example_provider_spec.rb
|
300
|
+
- spec/providers/example_spec.rb
|
298
301
|
- spec/providers/vk_spec.rb
|
299
302
|
- spec/rails_app/app/active_record/authentication.rb
|
300
303
|
- spec/rails_app/app/active_record/user.rb
|
301
304
|
- spec/rails_app/app/active_record/user_provider.rb
|
305
|
+
- spec/rails_app/app/assets/config/manifest.js
|
302
306
|
- spec/rails_app/app/controllers/sorcery_controller.rb
|
303
307
|
- spec/rails_app/app/helpers/application_helper.rb
|
304
308
|
- spec/rails_app/app/mailers/sorcery_mailer.rb
|
@@ -352,6 +356,8 @@ files:
|
|
352
356
|
- spec/spec.opts
|
353
357
|
- spec/spec_helper.rb
|
354
358
|
- spec/support/migration_helper.rb
|
359
|
+
- spec/support/providers/example.rb
|
360
|
+
- spec/support/providers/example_provider.rb
|
355
361
|
homepage: https://github.com/Sorcery/sorcery
|
356
362
|
licenses:
|
357
363
|
- MIT
|
@@ -366,15 +372,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
366
372
|
requirements:
|
367
373
|
- - ">="
|
368
374
|
- !ruby/object:Gem::Version
|
369
|
-
version: 2.
|
375
|
+
version: 2.4.9
|
370
376
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
371
377
|
requirements:
|
372
378
|
- - ">="
|
373
379
|
- !ruby/object:Gem::Version
|
374
380
|
version: '0'
|
375
381
|
requirements: []
|
376
|
-
|
377
|
-
rubygems_version: 2.7.7
|
382
|
+
rubygems_version: 3.0.8
|
378
383
|
signing_key:
|
379
384
|
specification_version: 4
|
380
385
|
summary: Magical authentication for Rails applications
|