rails-identity 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13d1f01c0a93ffb9ea2548258bf1b63a261085bf
4
- data.tar.gz: 4edfcaca8bc9d151a62799c54c1451fd51592196
3
+ metadata.gz: 069d960770fb7c3e3d60a2a7a7de61217a3d864c
4
+ data.tar.gz: f5367277a5aa44adc273009b11bedbf140258eac
5
5
  SHA512:
6
- metadata.gz: 2acceb4c9f742fd0ea740639f4b316e72d061e09dc56f46a6fe88bcadbfaedea0a9a9ddfbf12fcee5ed9f75d06b146c9c8d2a0ebdab429ec1ad07affd42f1ce5
7
- data.tar.gz: e981b7d80ad98a39af5a0ed1d69b2f0cfd488f8914dbaa5e398f70a9438b5bef5a88828fc40cfcb7bf16eb57d3449d7e9ae89029fd2a1c1e76b32e95b4544157
6
+ metadata.gz: 2c31e81c81d366e0f1b8b59e0e023715779f288899946d7c3ed283a317b66f27f4453270977f8e77e95e54d3f53bbdc08f6f354f021f6e9985ffce2e2353d7cb
7
+ data.tar.gz: ffca70e53b8d9e4e53ab383c3cfc9752774485b2f4f208a83916bd907e8037c23f8110fcc643685bda025d27be7605ce3a222d67ff51fc50aef15c8a47913c4c
@@ -9,7 +9,7 @@ module RailsIdentity
9
9
  #
10
10
  class SessionsController < ApplicationController
11
11
 
12
- prepend_before_action :require_token, except: [:create, :options]
12
+ prepend_before_action :require_auth, except: [:create, :options]
13
13
  before_action :get_session, only: [:show, :destroy]
14
14
  before_action :get_user, only: [:index]
15
15
 
@@ -38,6 +38,8 @@ module RailsIdentity
38
38
  # token based authentication. If the latter doesn't make sense, just use
39
39
  # the username and password approach.
40
40
  #
41
+ # A Repia::Errors::Unauthorized is thrown if user is not verified.
42
+ #
41
43
  def create
42
44
  @user = User.find_by_username(session_params[:username])
43
45
  if (@user && @user.authenticate(session_params[:password])) || get_user()
@@ -70,7 +72,7 @@ module RailsIdentity
70
72
  render body: "", status: 204
71
73
  else
72
74
  # :nocov:
73
- render_error 500, "Something went wrong. Oops!"
75
+ render_error 400, @session.errors.full_messages
74
76
  # :nocov:
75
77
  end
76
78
  end
@@ -80,15 +82,18 @@ module RailsIdentity
80
82
  ##
81
83
  # Get the specified or current session.
82
84
  #
83
- # An Repia::Errors::NotFound is raised if the session does not
85
+ # A Repia::Errors::NotFound is raised if the session does not
84
86
  # exist (or deleted due to expiration).
85
87
  #
86
- # An Repia::Errors::Unauthorized is raised if the authenticated user
88
+ # A Repia::Errors::Unauthorized is raised if the authenticated user
87
89
  # does not have authorization for the specified session.
88
90
  #
89
91
  def get_session
90
92
  session_id = params[:id]
91
93
  if session_id == "current"
94
+ if @auth_session.nil?
95
+ raise Repia::Errors::NotFound
96
+ end
92
97
  session_id = @auth_session.id
93
98
  end
94
99
  @session = find_object(Session, session_id)
@@ -9,9 +9,9 @@ module RailsIdentity
9
9
 
10
10
  # All except user creation requires a session token. Note that reset
11
11
  # token is also a legit session token, so :require_token will suffice.
12
- prepend_before_action :require_token, only: [:show, :destroy]
13
- prepend_before_action :accept_token, only: [:update, :create]
14
- prepend_before_action :require_admin_token, only: [:index]
12
+ prepend_before_action :require_auth, only: [:show, :destroy]
13
+ prepend_before_action :accept_auth, only: [:update, :create]
14
+ prepend_before_action :require_admin_auth, only: [:index]
15
15
 
16
16
  # Some actions must have a user specified.
17
17
  before_action :get_user, only: [:show, :destroy]
@@ -34,13 +34,15 @@ module RailsIdentity
34
34
  if @user.save
35
35
 
36
36
  # Save succeeded. Render the response based on the created user.
37
- render json: @user, except: [:verification_token, :reset_token, :password_digest], status: 201
37
+ render json: @user,
38
+ except: [:verification_token, :reset_token, :password_digest],
39
+ status: 201
38
40
 
39
41
  # Then, issue the verification token and send the email for
40
42
  # verification.
41
43
  @user.issue_token(:verification_token)
42
44
  @user.save
43
- UserMailer.email_verification(@user).deliver_later
45
+ user_mailer.email_verification(@user).deliver_later
44
46
  else
45
47
  render_errors 400, @user.errors.full_messages
46
48
  end
@@ -54,28 +56,29 @@ module RailsIdentity
54
56
  end
55
57
 
56
58
  ##
57
- # Patches the user. Some overloading operations here. There are five
58
- # notable ways to update a user.
59
+ # Patches the user object. There are four notable operations:
59
60
  #
60
- # - Issue a reset token
61
- # If params has :issue_reset_token set to true, the action will
62
- # issue a reset token for the user and returns 204. Yes, 204 No
63
- # Content.
64
- # - Reset the password
65
- # Two ways to reset password:
66
- # - Provide the old password along with the new password and
67
- # confirmation.
68
- # - Provide the reset token as the auth token.
69
- # - Issue a verification token
70
- # - Change other data
61
+ # - issue reset token
62
+ # - issue verification token
63
+ # - change password
64
+ # - others
65
+ #
66
+ # Issuing either reset token or verification token requires NO
67
+ # authentication. However, for that reason, the request does not get any
68
+ # meaningful response. Instead, an email is sent out for either request.
69
+ #
70
+ # For changing password, there are two ways. One is to use old password
71
+ # and the other is to use reset token.
72
+ #
73
+ # Otherwise, it's a normal update operation.
71
74
  #
72
75
  def update
73
76
  if params[:issue_reset_token] || params[:issue_verification_token]
74
77
  # For issuing a reset token, one does not need an auth token. so do
75
- # not authorize the request.
78
+ # not authorize the request. For consistency, we require the id to
79
+ # be "current".
76
80
  raise Repia::Errors::Unauthorized unless params[:id] == "current"
77
81
  get_user_for_token()
78
- raise Repia::Errors::Unauthorized unless params[:username] == @user.username
79
82
  if params[:issue_reset_token]
80
83
  update_token(:reset_token)
81
84
  else
@@ -83,13 +86,7 @@ module RailsIdentity
83
86
  end
84
87
  else
85
88
  get_user()
86
- if params[:password]
87
- if params[:old_password]
88
- raise Repia::Errors::Unauthorized unless @user.authenticate(params[:old_password])
89
- else
90
- raise Repia::Errors::Unauthorized unless @token == @user.reset_token
91
- end
92
- end
89
+ allow_password_change? if params[:password]
93
90
  update_user(user_params)
94
91
  end
95
92
  end
@@ -102,13 +99,40 @@ module RailsIdentity
102
99
  render body: '', status: 204
103
100
  else
104
101
  # :nocov:
105
- render_error 500, "Something went wrong!"
102
+ render_error 400, @user.errors.full_messages
106
103
  # :nocov:
107
104
  end
108
105
  end
109
106
 
110
107
  protected
111
108
 
109
+ ##
110
+ # Override this method to app specific mailer.
111
+ #
112
+ def user_mailer
113
+ return UserMailer
114
+ end
115
+
116
+ ##
117
+ # Check if password change should be allowed. Two ways to do this: one
118
+ # is to use old password or to use a valid reset token.
119
+ #
120
+ # A Repia::Errors::Unauthorized is thrown for invalid old password or
121
+ # invalid reset token
122
+ #
123
+ def allow_password_change?
124
+ if params[:old_password]
125
+ unless @user.authenticate(params[:old_password])
126
+ raise Repia::Errors::Unauthorized
127
+ end
128
+ else
129
+ unless @token == @user.reset_token
130
+ raise Repia::Errors::Unauthorized
131
+ end
132
+ end
133
+ return true
134
+ end
135
+
112
136
  ##
113
137
  # This method normally updates the user using permitted params.
114
138
  #
@@ -128,9 +152,9 @@ module RailsIdentity
128
152
  @user.issue_token(kind)
129
153
  @user.save
130
154
  if kind == :reset_token
131
- UserMailer.password_reset(@user).deliver_later
155
+ user_mailer.password_reset(@user).deliver_later
132
156
  else
133
- UserMailer.email_verification(@user).deliver_later
157
+ user_mailer.email_verification(@user).deliver_later
134
158
  end
135
159
  render body: '', status: 204
136
160
  end
@@ -31,11 +31,69 @@ module RailsIdentity
31
31
  end
32
32
  end
33
33
 
34
+ ##
35
+ # Requires authentication. Either token or api key must be present.
36
+ #
37
+ def require_auth
38
+ logger.debug("Requiring any authentication")
39
+ get_auth
40
+ end
41
+
42
+ ##
43
+ # Requires admin authentication. Either token or api key must be present
44
+ # and it should be an admin's.
45
+ #
46
+ def require_admin_auth
47
+ logger.debug("Requiring any admin authentication")
48
+ get_auth(required_role: Roles::ADMIN)
49
+ end
50
+
51
+ ##
52
+ # Accepts authentication if present. Either token or api key is
53
+ # accepted.
54
+ #
55
+ def accept_auth
56
+ logger.debug("Accepting any authentication")
57
+ begin
58
+ get_auth
59
+ rescue StandardError => e
60
+ logger.error("Suppressing error: #{e.message}")
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Requires API key for authentication.
66
+ #
67
+ def require_api_key
68
+ logger.debug("Requiring api key")
69
+ get_api_key
70
+ end
71
+
72
+ ##
73
+ # Requires admin API key for authentication.
74
+ #
75
+ def require_admin_api_key
76
+ logger.debug("Requiring admin api key")
77
+ get_api_key(required_role: Roles::ADMIN)
78
+ end
79
+
80
+ ##
81
+ # Accepts API key for authentication if one is present.
82
+ #
83
+ def accept_api_key
84
+ logger.debug("Accepting api key")
85
+ begin
86
+ get_api_key
87
+ rescue StandardError => e
88
+ logger.error("Suppressing error: #{e.message}")
89
+ end
90
+ end
91
+
34
92
  ##
35
93
  # Requires a token.
36
94
  #
37
95
  def require_token
38
- logger.debug("Requires a token")
96
+ logger.debug("Requiring token")
39
97
  get_token
40
98
  end
41
99
 
@@ -44,7 +102,7 @@ module RailsIdentity
44
102
  # suppressed.
45
103
  #
46
104
  def accept_token()
47
- logger.debug("Accepts a token")
105
+ logger.debug("Accepting token")
48
106
  begin
49
107
  get_token()
50
108
  rescue StandardError => e
@@ -57,7 +115,7 @@ module RailsIdentity
57
115
  # issued for an admin user (role == 1000).
58
116
  #
59
117
  def require_admin_token
60
- logger.debug("Requires an admin token")
118
+ logger.debug("Requiring admin token")
61
119
  get_token(required_role: Roles::ADMIN)
62
120
  end
63
121
 
@@ -151,6 +209,9 @@ module RailsIdentity
151
209
  # Attempt to get a token for the session. Token must be specified in
152
210
  # query string or part of the JSON object.
153
211
  #
212
+ # Raises a Repia::Errors::Unauthorized if cached session has less role
213
+ # than what's required.
214
+ #
154
215
  def get_token(required_role: Roles::PUBLIC)
155
216
  token = params[:token]
156
217
  payload = get_token_payload(token)
@@ -160,11 +221,48 @@ module RailsIdentity
160
221
  if @auth_session.nil?
161
222
  @auth_session = verify_token_payload(token, payload,
162
223
  required_role: required_role)
224
+ @auth_session.role # NOTE: no-op
163
225
  Rails.cache.write("#{CACHE_PREFIX}-session-#{token}", @auth_session)
226
+ elsif @auth_session.role < required_role
227
+ raise Repia::Errors::Unauthorized
164
228
  end
165
229
  @auth_user = @auth_session.user
166
230
  @token = @auth_session.token
167
231
  end
168
232
 
233
+ ##
234
+ # Get API key from the request.
235
+ #
236
+ # Raises a Repia::Errors::Unauthorized if API key is not valid (or not
237
+ # provided).
238
+ #
239
+ def get_api_key(required_role: Roles::PUBLIC)
240
+ api_key = params[:api_key]
241
+ if api_key.nil?
242
+ # This case is not likely, but as a safeguard in case migration
243
+ # has not gone well.
244
+ # :nocov:
245
+ raise Repia::Errors::Unauthorized, "Invalid api key"
246
+ # :nocov:
247
+ end
248
+ auth_user = User.find_by_api_key(api_key)
249
+ if auth_user.nil? || auth_user.role < required_role
250
+ raise Repia::Errors::Unauthorized, "Invalid api key"
251
+ end
252
+ @auth_user = auth_user
253
+ @auth_session = nil
254
+ @token = nil
255
+ end
256
+
257
+ ##
258
+ # Get auth data from the request. The token takes the precedence.
259
+ #
260
+ def get_auth(required_role: Roles::USER)
261
+ if params[:token]
262
+ get_token(required_role: required_role)
263
+ else
264
+ get_api_key(required_role: required_role)
265
+ end
266
+ end
169
267
  end
170
268
  end
@@ -40,5 +40,12 @@ module RailsIdentity
40
40
  return false
41
41
  end
42
42
 
43
+ def role
44
+ if @role.nil?
45
+ @role = user.role
46
+ end
47
+ return @role
48
+ end
49
+
43
50
  end
44
51
  end
@@ -18,6 +18,7 @@ module RailsIdentity
18
18
  # re-issue the verification token.
19
19
  #
20
20
  def initialize(attributes = {})
21
+ attributes[:api_key] = SecureRandom.hex(32)
21
22
  super
22
23
  end
23
24
 
@@ -0,0 +1,10 @@
1
+ class AddApiKeyToUsers < ActiveRecord::Migration
2
+ def change
3
+ add_column :rails_identity_users, :api_key, :string
4
+ add_index :rails_identity_users, :api_key
5
+
6
+ RailsIdentity::User.find_each do |user|
7
+ user.update(api_key: SecureRandom.hex(32))
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsIdentity
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,124 @@
1
+ require 'test_helper'
2
+
3
+ module RailsIdentity
4
+
5
+ class TestsController < ApplicationController
6
+ def index
7
+ render json: {}, status: 200
8
+ end
9
+ end
10
+
11
+ class TestsControllerTest < ActionController::TestCase
12
+ setup do
13
+ Rails.cache.clear
14
+ @session = rails_identity_sessions(:one)
15
+ @token = @session.token
16
+ @admin_session = rails_identity_sessions(:admin_one)
17
+ @admin_token = @admin_session.token
18
+ @api_key = rails_identity_users(:one).api_key
19
+ @admin_api_key = rails_identity_users(:admin_one).api_key
20
+ # Rails.application.routes.draw do
21
+ RailsIdentity::Engine.routes.draw do
22
+ match "tests" => "tests#index", via: [:get]
23
+ end
24
+ @routes = Engine.routes
25
+ end
26
+
27
+ teardown do
28
+ RailsIdentity::Engine.routes.draw do
29
+ resources :sessions
30
+ match 'sessions(/:id)' => 'sessions#options', via: [:options]
31
+
32
+ resources :users
33
+ match 'users(/:id)' => 'users#options', via: [:options]
34
+ end
35
+ end
36
+
37
+ test "require only token" do
38
+ class ::RailsIdentity::TestsController < ApplicationController
39
+ reset_callbacks :process_action
40
+ before_action :require_token, only: [:index]
41
+ def index
42
+ render json: {}, status: 200
43
+ end
44
+ end
45
+ get :index, token: @token
46
+ assert_response :success
47
+ get :index
48
+ assert_response 401
49
+ end
50
+
51
+ test "require only admin token" do
52
+ class ::RailsIdentity::TestsController < ApplicationController
53
+ reset_callbacks :process_action
54
+ before_action :require_admin_token, only: [:index]
55
+ def index
56
+ render json: {}, status: 200
57
+ end
58
+ end
59
+ get :index, token: @admin_token
60
+ assert_response :success
61
+ Rails.cache.write("#{CACHE_PREFIX}-session-#{@token}", @session)
62
+ get :index, token: @token
63
+ assert_response 401
64
+ end
65
+
66
+ test "accept only token" do
67
+ class ::RailsIdentity::TestsController < ApplicationController
68
+ reset_callbacks :process_action
69
+ before_action :accept_token, only: [:index]
70
+ def index
71
+ render json: {}, status: 200
72
+ end
73
+ end
74
+ get :index, token: @token
75
+ assert_response :success
76
+ get :index
77
+ assert_response :success
78
+ end
79
+
80
+ test "require only api key" do
81
+ class ::RailsIdentity::TestsController < ApplicationController
82
+ reset_callbacks :process_action
83
+ before_action :require_api_key, only: [:index]
84
+ def index
85
+ render json: {}, status: 200
86
+ end
87
+ end
88
+ get :index, api_key: @api_key
89
+ assert_response :success
90
+ get :index, token: @token
91
+ assert_response 401
92
+ get :index
93
+ assert_response 401
94
+ end
95
+
96
+ test "require only admin api key" do
97
+ class ::RailsIdentity::TestsController < ApplicationController
98
+ reset_callbacks :process_action
99
+ before_action :require_admin_api_key, only: [:index]
100
+ def index
101
+ render json: {}, status: 200
102
+ end
103
+ end
104
+ get :index, api_key: @admin_api_key
105
+ assert_response :success
106
+ get :index, api_key: @api_key
107
+ assert_response 401
108
+ end
109
+
110
+ test "accept only api key" do
111
+ class ::RailsIdentity::TestsController < ApplicationController
112
+ reset_callbacks :process_action
113
+ before_action :accept_api_key, only: [:index]
114
+ def index
115
+ render json: {}, status: 200
116
+ end
117
+ end
118
+ get :index, api_key: @api_key
119
+ assert_response :success
120
+ get :index
121
+ assert_response :success
122
+ end
123
+ end
124
+ end