rails_api_auth 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +159 -2
- data/app/controllers/oauth2_controller.rb +9 -9
- data/app/lib/login_not_found.rb +3 -0
- data/app/models/login.rb +55 -8
- data/app/services/facebook_authenticator.rb +15 -10
- data/db/migrate/20150709221755_create_logins.rb +2 -1
- data/lib/rails_api_auth.rb +26 -0
- data/lib/rails_api_auth/authentication.rb +67 -11
- data/lib/rails_api_auth/engine.rb +1 -4
- data/lib/rails_api_auth/version.rb +1 -1
- data/spec/dummy/app/controllers/authenticated_controller.rb +2 -2
- data/spec/dummy/app/controllers/custom_authenticated_controller.rb +21 -0
- data/spec/dummy/app/models/account.rb +5 -0
- data/spec/dummy/config/application.rb +0 -2
- data/spec/dummy/config/environments/development.rb +2 -2
- data/spec/dummy/config/environments/production.rb +1 -1
- data/spec/dummy/config/environments/test.rb +1 -1
- data/spec/dummy/config/initializers/assets.rb +1 -1
- data/spec/dummy/config/initializers/cookies_serializer.rb +1 -1
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +1 -1
- data/spec/dummy/config/initializers/rails_api_auth.rb +8 -0
- data/spec/dummy/config/initializers/secret_token.rb +1 -0
- data/spec/dummy/config/initializers/session_store.rb +1 -1
- data/spec/dummy/config/routes.rb +2 -1
- data/spec/dummy/config/secrets.yml +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20150803185817_create_accounts.rb +12 -0
- data/spec/dummy/db/schema.rb +9 -9
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +21 -11
- data/spec/dummy/log/test.log +11767 -6995
- data/spec/factories/accounts.rb +6 -0
- data/spec/factories/logins.rb +2 -2
- data/spec/models/login_spec.rb +10 -10
- data/spec/requests/authenticated_spec.rb +29 -26
- data/spec/requests/custom_authenticated_spec.rb +45 -0
- data/spec/requests/oauth2_spec.rb +30 -34
- data/spec/services/facebook_authenticator_spec.rb +15 -17
- data/spec/spec_helper.rb +10 -3
- metadata +17 -38
- data/app/controllers/rails_api_auth/application_controller.rb +0 -7
- data/config/initializers/facebook.rb +0 -6
- data/lib/tasks/rails_api_auth_tasks.rake +0 -4
- data/spec/dummy/db/migrate/20150709221900_create_users.rb +0 -11
- data/spec/dummy/db/production.sqlite3 +0 -0
data/spec/factories/logins.rb
CHANGED
data/spec/models/login_spec.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe Login do
|
4
|
-
it
|
5
|
-
|
6
|
-
|
2
|
+
it 'belongs to the configured user model' do
|
3
|
+
expect(subject).to belong_to(:account).with_foreign_key(:user_id)
|
4
|
+
end
|
5
|
+
|
6
|
+
it { is_expected.to validate_presence_of(:identification) }
|
7
7
|
|
8
8
|
it 'validates presence of either password or Facebook UID' do
|
9
|
-
login = described_class.new(
|
9
|
+
login = described_class.new(identification: 'test@example.com', oauth2_token: 'token')
|
10
10
|
|
11
11
|
expect(login).to_not be_valid
|
12
12
|
end
|
13
13
|
|
14
14
|
it "doesn't validate presence of password when Facebook UID is present" do
|
15
|
-
login = described_class.new(
|
15
|
+
login = described_class.new(identification: 'test@example.com', oauth2_token: 'token', facebook_uid: '123')
|
16
16
|
|
17
17
|
expect(login).to be_valid
|
18
18
|
end
|
19
19
|
|
20
20
|
it "doesn't validate presence of Facebook UID when password is present" do
|
21
|
-
login = described_class.new(
|
21
|
+
login = described_class.new(identification: 'test@example.com', oauth2_token: 'token', password: '123')
|
22
22
|
|
23
23
|
expect(login).to be_valid
|
24
24
|
end
|
@@ -61,8 +61,8 @@ describe Login do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
context 'when the supplied token is invalid' do
|
64
|
-
it 'raises an
|
65
|
-
expect { subject.consume_single_use_oauth2_token!('invalid token') }.to raise_error(Login::
|
64
|
+
it 'raises an InvalidOAuth2Token' do
|
65
|
+
expect { subject.consume_single_use_oauth2_token!('invalid token') }.to raise_error(Login::InvalidOAuth2Token)
|
66
66
|
end
|
67
67
|
end
|
68
68
|
end
|
@@ -1,47 +1,50 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe 'Authenticated route' do
|
4
|
-
let!(:login) { create(:login) }
|
5
|
-
let(:headers) do
|
6
|
-
{
|
7
|
-
'Authorization': "Bearer #{login.oauth2_token}"
|
8
|
-
}
|
9
|
-
end
|
10
|
-
|
1
|
+
describe 'an authenticated route' do
|
11
2
|
subject { get '/authenticated', {}, headers }
|
12
3
|
|
13
|
-
|
14
|
-
subject
|
4
|
+
let(:headers) { {} }
|
15
5
|
|
16
|
-
|
17
|
-
|
6
|
+
context 'when a valid Bearer token is present' do
|
7
|
+
let(:login) { create(:login) }
|
8
|
+
let(:headers) do
|
9
|
+
{ 'Authorization' => "Bearer #{login.oauth2_token}" }
|
10
|
+
end
|
18
11
|
|
19
|
-
|
20
|
-
|
12
|
+
it 'assigns the authenticated login to @current_login' do
|
13
|
+
subject
|
21
14
|
|
22
|
-
|
23
|
-
|
15
|
+
expect(assigns[:current_login]).to eq(login)
|
16
|
+
end
|
24
17
|
|
25
|
-
|
26
|
-
|
18
|
+
it "responds with the actual action's status" do
|
19
|
+
subject
|
27
20
|
|
28
|
-
|
21
|
+
expect(response).to have_http_status(201)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "responds with the actual action's body" do
|
25
|
+
subject
|
26
|
+
|
27
|
+
expect(response.body).to eql('zuper content')
|
28
|
+
end
|
29
29
|
end
|
30
30
|
|
31
|
-
context 'no token' do
|
31
|
+
context 'when no valid Bearer token is present' do
|
32
|
+
it 'does not assign the authenticated login to @current_login' do
|
33
|
+
subject
|
32
34
|
|
33
|
-
|
35
|
+
expect(assigns[:current_login]).to be_nil
|
36
|
+
end
|
34
37
|
|
35
|
-
it '401' do
|
38
|
+
it 'responds with status 401' do
|
36
39
|
subject
|
37
40
|
|
38
|
-
expect(response
|
41
|
+
expect(response).to have_http_status(401)
|
39
42
|
end
|
40
43
|
|
41
44
|
it 'responds with an empty body' do
|
42
45
|
subject
|
43
46
|
|
44
|
-
expect(response.body).to be_empty
|
47
|
+
expect(response.body.strip).to be_empty
|
45
48
|
end
|
46
49
|
end
|
47
50
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
describe 'a custom authenticated route' do
|
2
|
+
subject { get '/custom-authenticated', {}, headers }
|
3
|
+
|
4
|
+
let(:account) { create(:account) }
|
5
|
+
let(:login) { create(:login, account: account) }
|
6
|
+
let(:headers) do
|
7
|
+
{ 'Authorization' => "Bearer #{login.oauth2_token}" }
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when the block returns true' do
|
11
|
+
let(:account) { create(:account, first_name: 'user x') }
|
12
|
+
|
13
|
+
it 'assigns the authenticated login to @current_login' do
|
14
|
+
subject
|
15
|
+
|
16
|
+
expect(assigns[:current_login]).to eq(login)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "responds with the actual action's status" do
|
20
|
+
subject
|
21
|
+
|
22
|
+
expect(response).to have_http_status(201)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "responds with the actual action's body" do
|
26
|
+
subject
|
27
|
+
|
28
|
+
expect(response.body).to eql('zuper content')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the block returns false' do
|
33
|
+
it 'responds with status 401' do
|
34
|
+
subject
|
35
|
+
|
36
|
+
expect(response).to have_http_status(401)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'responds with an empty body' do
|
40
|
+
subject
|
41
|
+
|
42
|
+
expect(response.body.strip).to be_empty
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,17 +1,14 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe 'Oauth2 API' do
|
4
|
-
|
5
2
|
let!(:login) { create(:login) }
|
6
3
|
|
7
4
|
describe 'POST /token' do
|
8
|
-
let(:params) { { grant_type: 'password', username: login.
|
5
|
+
let(:params) { { grant_type: 'password', username: login.identification, password: login.password } }
|
9
6
|
|
10
7
|
subject { post '/token', params }
|
11
8
|
|
12
9
|
context 'for grant_type "password"' do
|
13
10
|
context 'with valid login credentials' do
|
14
|
-
it '
|
11
|
+
it 'responds with status 200' do
|
15
12
|
subject
|
16
13
|
|
17
14
|
expect(response).to have_http_status(200)
|
@@ -25,7 +22,7 @@ describe 'Oauth2 API' do
|
|
25
22
|
end
|
26
23
|
|
27
24
|
context 'with invalid login credentials' do
|
28
|
-
let(:params) { { grant_type: 'password', username:
|
25
|
+
let(:params) { { grant_type: 'password', username: login.identification, password: 'badpassword' } }
|
29
26
|
|
30
27
|
it 'responds with status 400' do
|
31
28
|
subject
|
@@ -43,62 +40,62 @@ describe 'Oauth2 API' do
|
|
43
40
|
|
44
41
|
context 'for grant_type "facebook_auth_code"' do
|
45
42
|
let(:secret) { described_class::FB_APP_SECRET }
|
46
|
-
let(:params) { { grant_type: 'facebook_auth_code', auth_code: '
|
47
|
-
let(:facebook_email) { login.
|
48
|
-
let(:facebook_data)
|
43
|
+
let(:params) { { grant_type: 'facebook_auth_code', auth_code: 'authcode' } }
|
44
|
+
let(:facebook_email) { login.identification }
|
45
|
+
let(:facebook_data) do
|
49
46
|
{
|
50
|
-
id:
|
51
|
-
email:
|
47
|
+
id: '1238190321',
|
48
|
+
email: facebook_email
|
52
49
|
}
|
53
50
|
end
|
54
51
|
|
55
52
|
before do
|
56
|
-
stub_request(:get,
|
57
|
-
stub_request(:get,
|
53
|
+
stub_request(:get, 'https://graph.facebook.com/oauth/access_token?client_id=app_id&client_secret=app_secret&code=authcode&redirect_uri=redirect_uri').to_return({ body: '{ "access_token": "access_token" }' })
|
54
|
+
stub_request(:get, 'https://graph.facebook.com/me?access_token=access_token').to_return({ body: JSON.generate(facebook_data), headers: { 'Content-Type' => 'application/json' } })
|
58
55
|
end
|
59
56
|
|
60
|
-
context 'when a login with the
|
57
|
+
context 'when a login with for the Facebook account exists' do
|
61
58
|
it 'connects the login to the Facebook account' do
|
62
59
|
subject
|
63
60
|
|
64
61
|
expect(login.reload.facebook_uid).to eq(facebook_data[:id])
|
65
62
|
end
|
66
63
|
|
67
|
-
it '
|
64
|
+
it 'responds with status 200' do
|
68
65
|
subject
|
69
66
|
|
70
67
|
expect(response).to have_http_status(200)
|
71
68
|
end
|
72
69
|
|
73
|
-
it
|
70
|
+
it "responds with the login's OAuth 2.0 token" do
|
74
71
|
subject
|
75
72
|
|
76
73
|
expect(response.body).to be_json_eql({ access_token: login.oauth2_token }.to_json)
|
77
74
|
end
|
78
75
|
end
|
79
76
|
|
80
|
-
context 'when no login
|
77
|
+
context 'when no login for the Facebook account exists' do
|
81
78
|
let(:facebook_email) { Faker::Internet.email }
|
82
79
|
|
83
|
-
it '
|
80
|
+
it 'responds with status 200' do
|
84
81
|
subject
|
85
82
|
|
86
83
|
expect(response).to have_http_status(200)
|
87
84
|
end
|
88
85
|
|
89
|
-
it 'creates a login
|
90
|
-
expect { subject }.to change { Login.where(
|
86
|
+
it 'creates a login for the Facebook account' do
|
87
|
+
expect { subject }.to change { Login.where(identification: facebook_email).count }.by(1)
|
91
88
|
end
|
92
89
|
|
93
|
-
it
|
90
|
+
it "responds with the login's OAuth 2.0 token" do
|
94
91
|
subject
|
95
|
-
login = Login.
|
92
|
+
login = Login.where(identification: facebook_email).first
|
96
93
|
|
97
94
|
expect(response.body).to be_json_eql({ access_token: login.oauth2_token }.to_json)
|
98
95
|
end
|
99
96
|
end
|
100
97
|
|
101
|
-
context 'when no
|
98
|
+
context 'when no Facebook auth code is sent' do
|
102
99
|
let(:params) { { grant_type: 'facebook_auth_code' } }
|
103
100
|
|
104
101
|
it 'responds with status 400' do
|
@@ -107,7 +104,7 @@ describe 'Oauth2 API' do
|
|
107
104
|
expect(response).to have_http_status(400)
|
108
105
|
end
|
109
106
|
|
110
|
-
it 'responds with a
|
107
|
+
it 'responds with a "no_authorization_code" error' do
|
111
108
|
subject
|
112
109
|
|
113
110
|
expect(response.body).to be_json_eql({ error: 'no_authorization_code' }.to_json)
|
@@ -116,19 +113,19 @@ describe 'Oauth2 API' do
|
|
116
113
|
|
117
114
|
context 'when Facebook responds with an error' do
|
118
115
|
before do
|
119
|
-
stub_request(:get,
|
116
|
+
stub_request(:get, 'https://graph.facebook.com/me?access_token=access_token').to_return(status: 422)
|
120
117
|
end
|
121
118
|
|
122
|
-
it 'responds with status
|
119
|
+
it 'responds with status 502' do
|
123
120
|
subject
|
124
121
|
|
125
|
-
expect(response).to have_http_status(
|
122
|
+
expect(response).to have_http_status(502)
|
126
123
|
end
|
127
124
|
|
128
125
|
it 'responds with an empty response body' do
|
129
126
|
subject
|
130
127
|
|
131
|
-
expect(response.body).to eql('')
|
128
|
+
expect(response.body.strip).to eql('')
|
132
129
|
end
|
133
130
|
end
|
134
131
|
end
|
@@ -142,7 +139,7 @@ describe 'Oauth2 API' do
|
|
142
139
|
expect(response).to have_http_status(400)
|
143
140
|
end
|
144
141
|
|
145
|
-
it 'responds with an
|
142
|
+
it 'responds with an "unsupported_grant_type" error' do
|
146
143
|
subject
|
147
144
|
|
148
145
|
expect(response.body).to be_json_eql({ error: 'unsupported_grant_type' }.to_json)
|
@@ -155,29 +152,28 @@ describe 'Oauth2 API' do
|
|
155
152
|
|
156
153
|
subject { post '/revoke', params }
|
157
154
|
|
158
|
-
it '
|
155
|
+
it 'responds with status 200' do
|
159
156
|
subject
|
160
157
|
|
161
158
|
expect(response).to have_http_status(200)
|
162
159
|
end
|
163
160
|
|
164
|
-
it
|
161
|
+
it "resets the login's OAuth 2.0 token" do
|
165
162
|
expect { subject }.to change { login.reload.oauth2_token }
|
166
163
|
|
167
164
|
subject
|
168
165
|
end
|
169
166
|
|
170
|
-
context 'for an
|
167
|
+
context 'for an invalid token' do
|
171
168
|
let(:params) { { token_type_hint: 'access_token', token: 'badtoken' } }
|
172
169
|
|
173
|
-
it '
|
170
|
+
it 'responds with status 200' do
|
174
171
|
subject
|
175
172
|
|
176
173
|
expect(response).to have_http_status(200)
|
177
174
|
end
|
178
175
|
|
179
176
|
it "doesn't reset any logins' token" do
|
180
|
-
|
181
177
|
expect_any_instance_of(LoginNotFound).to receive(:refresh_oauth2_token!)
|
182
178
|
|
183
179
|
subject
|
@@ -1,50 +1,48 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe FacebookAuthenticator do
|
4
|
-
describe '#authenticate' do
|
2
|
+
describe '#authenticate!' do
|
5
3
|
let(:auth_code) { 'authcode' }
|
6
4
|
let(:email) { 'email@facebook.com' }
|
7
5
|
let(:facebook_data) do
|
8
6
|
{
|
9
|
-
id:
|
10
|
-
email:
|
7
|
+
id: '1238190321',
|
8
|
+
email: email
|
11
9
|
}
|
12
10
|
end
|
13
11
|
let(:response_with_fb_token) { { body: '{ "access_token": "access_token" }' } }
|
14
12
|
let(:response_with_fb_user) { { body: JSON.generate(facebook_data), headers: { 'Content-Type' => 'application/json' } } }
|
15
13
|
let(:login) { double('login') }
|
16
14
|
|
17
|
-
subject { described_class.new(auth_code).authenticate }
|
15
|
+
subject { described_class.new(auth_code).authenticate! }
|
18
16
|
|
19
17
|
before do
|
20
|
-
stub_request(:get,
|
21
|
-
stub_request(:get,
|
18
|
+
stub_request(:get, 'https://graph.facebook.com/oauth/access_token?client_id=app_id&client_secret=app_secret&code=authcode&redirect_uri=redirect_uri').to_return({ body: '{ "access_token": "access_token" }' })
|
19
|
+
stub_request(:get, 'https://graph.facebook.com/me?access_token=access_token').to_return(response_with_fb_user)
|
22
20
|
end
|
23
21
|
|
24
|
-
context '
|
22
|
+
context 'when no login for the Facebook account exists' do
|
25
23
|
let(:login_attributes) do
|
26
24
|
{
|
27
|
-
|
28
|
-
facebook_uid:
|
25
|
+
identification: facebook_data[:email],
|
26
|
+
facebook_uid: facebook_data[:id]
|
29
27
|
}
|
30
28
|
end
|
31
29
|
|
32
30
|
before do
|
33
|
-
allow(Login).to receive(:create!).with(login_attributes).and_return
|
31
|
+
allow(Login).to receive(:create!).with(login_attributes).and_return(login)
|
34
32
|
end
|
35
33
|
|
36
|
-
it 'returns a login created from
|
37
|
-
expect(subject).to eql
|
34
|
+
it 'returns a login created from the Facebook account' do
|
35
|
+
expect(subject).to eql(login)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
context '
|
39
|
+
context 'when a login for the Facebook account exists already' do
|
42
40
|
before do
|
43
|
-
expect(Login).to receive(:
|
41
|
+
expect(Login).to receive(:where).with(identification: facebook_data[:email]).and_return([login])
|
44
42
|
allow(login).to receive(:update_attributes!).with(facebook_uid: facebook_data[:id])
|
45
43
|
end
|
46
44
|
|
47
|
-
it 'connects login to
|
45
|
+
it 'connects the login to the Facebook account' do
|
48
46
|
expect(login).to receive(:update_attributes!).with(facebook_uid: facebook_data[:id])
|
49
47
|
|
50
48
|
subject
|
data/spec/spec_helper.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
ENV['RAILS_ENV'] ||= 'test'
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'simplecov'
|
4
|
+
SimpleCov.start do ||
|
5
|
+
minimum_coverage 95
|
6
|
+
refuse_coverage_drop
|
7
|
+
end
|
8
|
+
|
9
|
+
require File.expand_path('../dummy/config/environment.rb', __FILE__)
|
4
10
|
require 'rspec/rails'
|
5
|
-
# require 'rspec/autorun'
|
6
11
|
require 'factory_girl_rails'
|
7
12
|
require 'faker'
|
8
13
|
|
9
14
|
Rails.backtrace_cleaner.remove_silencers!
|
10
15
|
|
11
|
-
|
16
|
+
%w(factories support).each do |path|
|
17
|
+
Dir["#{File.dirname(__FILE__)}/#{path}/**/*.rb"].each { |f| require f }
|
18
|
+
end
|
12
19
|
|
13
20
|
RSpec.configure do |config|
|
14
21
|
config.mock_with :rspec
|