rails-identity 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +37 -0
- data/app/assets/javascripts/rails_identity/application.js +13 -0
- data/app/assets/javascripts/rails_identity/sessions.js +2 -0
- data/app/assets/javascripts/rails_identity/users.js +2 -0
- data/app/assets/stylesheets/rails_identity/application.css +15 -0
- data/app/assets/stylesheets/rails_identity/sessions.css +4 -0
- data/app/assets/stylesheets/rails_identity/users.css +4 -0
- data/app/controllers/rails_identity/application_controller.rb +200 -0
- data/app/controllers/rails_identity/sessions_controller.rb +108 -0
- data/app/controllers/rails_identity/users_controller.rb +168 -0
- data/app/helpers/rails_identity/application_helper.rb +19 -0
- data/app/helpers/rails_identity/sessions_helper.rb +4 -0
- data/app/helpers/rails_identity/users_helper.rb +4 -0
- data/app/jobs/rails_identity/sessions_cleanup_job.rb +13 -0
- data/app/mailers/application_mailer.rb +4 -0
- data/app/mailers/rails_identity/user_mailer.rb +14 -0
- data/app/models/rails_identity/session.rb +44 -0
- data/app/models/rails_identity/user.rb +48 -0
- data/app/views/layouts/mailer.html.erb +5 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/layouts/rails_identity/application.html.erb +14 -0
- data/app/views/rails_identity/user_mailer/email_verification.html.erb +12 -0
- data/app/views/rails_identity/user_mailer/email_verification.text.erb +13 -0
- data/app/views/rails_identity/user_mailer/password_reset.html.erb +14 -0
- data/app/views/rails_identity/user_mailer/password_reset.text.erb +15 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20160323210013_create_rails_identity_users.rb +13 -0
- data/db/migrate/20160323210017_create_rails_identity_sessions.rb +12 -0
- data/db/migrate/20160401223433_add_reset_token_to_users.rb +5 -0
- data/db/migrate/20160411215917_add_verification_token_to_users.rb +10 -0
- data/db/migrate/20160414145851_add_api_key_to_users.rb +5 -0
- data/lib/rails_identity/engine.rb +9 -0
- data/lib/rails_identity/version.rb +3 -0
- data/lib/rails_identity.rb +52 -0
- data/lib/tasks/rails_identity_tasks.rake +4 -0
- data/test/controllers/rails_identity/sessions_controller_test.rb +192 -0
- data/test/controllers/rails_identity/users_controller_test.rb +253 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +42 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +215 -0
- data/test/dummy/log/test.log +280622 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/A5C/3F0/rails-identity-0.0.1-session-1 +0 -0
- data/test/fixtures/rails_identity/sessions.yml +36 -0
- data/test/fixtures/rails_identity/users.yml +24 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/jobs/rails_identity/sessions_cleanup_job_test.rb +9 -0
- data/test/mailers/previews/rails_identity/user_mailer_preview.rb +6 -0
- data/test/mailers/rails_identity/user_mailer_test.rb +9 -0
- data/test/models/rails_identity/session_test.rb +26 -0
- data/test/models/rails_identity/user_test.rb +54 -0
- data/test/rails_identity_test.rb +7 -0
- data/test/test_helper.rb +33 -0
- metadata +297 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f8999b0f579047f638bec461d6b469bc46b85319
|
4
|
+
data.tar.gz: 3412103b78681075aef4c59610db1a2bb17db352
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 120410b35e89556e779f13cae65aad0c68c57e0d3353094297ad8b7eb1e32c5ced2ae6ebd6bf5b6dbe158b587e8bcb08a0d73ae0ab5993c77e48d2c529dd3658
|
7
|
+
data.tar.gz: efe61e96f68ceefd497de74573a5b75c8d8ddcbe6a5c5273aec7bbd4dc6d071c4a98ba91dd060ed5bafe4170d9ffe6f81220b0b2234773a6d04334f6c715ccf2
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'RailsIdentity'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module RailsIdentity
|
2
|
+
|
3
|
+
##
|
4
|
+
# The root application controller class in rails-identity.
|
5
|
+
#
|
6
|
+
class ApplicationController < ActionController::Base
|
7
|
+
include ApplicationHelper
|
8
|
+
|
9
|
+
# This is a catch-all.
|
10
|
+
rescue_from StandardError do |exception|
|
11
|
+
# :nocov:
|
12
|
+
logger.error exception.message
|
13
|
+
render_error 500, "Unknown error occurred: #{exception.message}"
|
14
|
+
# :nocov:
|
15
|
+
end
|
16
|
+
|
17
|
+
# Most actions require a session token. If token is invalid, rescue the
|
18
|
+
# exception and throw an HTTP 401 response.
|
19
|
+
rescue_from Errors::InvalidTokenError do |exception|
|
20
|
+
logger.error exception.message
|
21
|
+
render_error 401, "Invalid token"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Some actions require a resource object via id. If no such object
|
25
|
+
# exists, throw an HTTP 404 response.
|
26
|
+
rescue_from Errors::ObjectNotFoundError do |exception|
|
27
|
+
logger.error exception.message
|
28
|
+
render_error 404, exception.message
|
29
|
+
end
|
30
|
+
|
31
|
+
# The request is authenticated but not authorized for the specified
|
32
|
+
# action. Throw an HTTP 401 response.
|
33
|
+
#
|
34
|
+
rescue_from Errors::UnauthorizedError do |exception|
|
35
|
+
logger.error exception.message
|
36
|
+
render_error 401, "Unauthorized request"
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Renders a generic OPTIONS response. The actual controller must
|
41
|
+
# override this action if desired to have specific OPTIONS handling
|
42
|
+
# logic.
|
43
|
+
#
|
44
|
+
def options()
|
45
|
+
# echo back access-control-request-headers
|
46
|
+
if request.headers["Access-Control-Request-Headers"]
|
47
|
+
response["Access-Control-Allow-Headers"] = request.headers["Access-Control-Request-Headers"]
|
48
|
+
end
|
49
|
+
render body: "", status: 200
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
##
|
55
|
+
# Helper method to get the user object in the request context. There
|
56
|
+
# are two ways to specify the user id--one in the routing or the auth
|
57
|
+
# context. Only admin can actually specify the user id in the routing.
|
58
|
+
#
|
59
|
+
# A Errors::UnauthorizedError is raised if the authenticated user
|
60
|
+
# is not authorized for the specified user information.
|
61
|
+
#
|
62
|
+
# A Errors::ObjectNotFoundError is raised if the specified user cannot
|
63
|
+
# be found.
|
64
|
+
#
|
65
|
+
def get_user(fallback: true)
|
66
|
+
user_id = params[:user_id]
|
67
|
+
logger.debug("Attempting to get user #{user_id}")
|
68
|
+
if !user_id.nil? && user_id != "current"
|
69
|
+
@user = find_object(User, params[:user_id]) # will throw error if nil
|
70
|
+
unless authorized?(@user)
|
71
|
+
raise Errors::UnauthorizedError, "Not authorized to access user #{user_id}"
|
72
|
+
end
|
73
|
+
elsif fallback || user_id == "current"
|
74
|
+
@user = @auth_user
|
75
|
+
else
|
76
|
+
# :nocov:
|
77
|
+
raise Errors::ObjectNotFoundError, "User #{user_id} does not exist"
|
78
|
+
# :nocov:
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Finds an object by model and UUID and throws an error (which will be
|
84
|
+
# caught and re-thrown as an HTTP error.)
|
85
|
+
#
|
86
|
+
# A Errors::ObjectNotFoundError is raised if specified to do so when
|
87
|
+
# the object could not be found using the uuid.
|
88
|
+
#
|
89
|
+
def find_object(model, uuid, error: Errors::ObjectNotFoundError)
|
90
|
+
logger.debug("Attempting to get #{model.name} #{uuid}")
|
91
|
+
obj = model.find_by_uuid(uuid)
|
92
|
+
if obj.nil? && !error.nil?
|
93
|
+
raise error, "#{model.name} #{uuid} cannot be found"
|
94
|
+
end
|
95
|
+
return obj
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Attempt to get a token for the session. Token must be specified in query
|
100
|
+
# string or part of the JSON object.
|
101
|
+
#
|
102
|
+
# A Errors::InvalidTokenError is raised if the JWT is malformed or not
|
103
|
+
# valid against its secret.
|
104
|
+
#
|
105
|
+
def get_token(required_role: Roles::PUBLIC)
|
106
|
+
token = params[:token]
|
107
|
+
|
108
|
+
# Attempt to decode token w/o secret first to see if well-formed and
|
109
|
+
# not expired.
|
110
|
+
begin
|
111
|
+
decoded = JWT.decode token, nil, false
|
112
|
+
rescue JWT::DecodeError => e
|
113
|
+
logger.error("Token decode error: #{e.message}")
|
114
|
+
raise Errors::InvalidTokenError, "Invalid token: #{token}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# At this point, we know that the token is not expired and
|
118
|
+
# well formatted. Find out if the payload is well defined.
|
119
|
+
payload = decoded[0]
|
120
|
+
if payload.nil?
|
121
|
+
logger.error("Token payload is nil: #{token}")
|
122
|
+
raise Errors::InvalidTokenError, "Invalid token payload: #{token}"
|
123
|
+
end
|
124
|
+
|
125
|
+
user_uuid = payload["user_uuid"]
|
126
|
+
session_uuid = payload["session_uuid"]
|
127
|
+
if user_uuid.nil? || session_uuid.nil?
|
128
|
+
logger.error("User UUID or session UUID is nil")
|
129
|
+
raise Errors::InvalidTokenError, "Invalid token payload content: #{token}"
|
130
|
+
end
|
131
|
+
logger.debug("Token well formatted for user #{user_uuid}, session #{session_uuid}")
|
132
|
+
|
133
|
+
# Look up the cache. If present, use it and skip the verification.
|
134
|
+
@auth_session = Rails.cache.fetch("#{CACHE_PREFIX}-session-#{session_uuid}")
|
135
|
+
if @auth_session.nil?
|
136
|
+
logger.debug("Cache miss. Try database.")
|
137
|
+
auth_user = User.find_by_uuid(user_uuid)
|
138
|
+
if auth_user.nil? || auth_user.role < required_role
|
139
|
+
raise Errors::InvalidTokenError, "Well-formed but invalid user token: #{token}"
|
140
|
+
end
|
141
|
+
@auth_session = Session.find_by_uuid(session_uuid)
|
142
|
+
if @auth_session.nil?
|
143
|
+
raise Errors::InvalidTokenError, "Well-formed but invalid session token: #{token}"
|
144
|
+
end
|
145
|
+
JWT.decode token, @auth_session.secret, true
|
146
|
+
logger.debug("Token well formatted and verified. Set cache.")
|
147
|
+
Rails.cache.write("#{CACHE_PREFIX}-session-#{session_uuid}", @auth_session)
|
148
|
+
end
|
149
|
+
@auth_user = @auth_session.user
|
150
|
+
@token = @auth_session.token
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# Requires a token.
|
155
|
+
#
|
156
|
+
def require_token
|
157
|
+
logger.debug("Requires a token")
|
158
|
+
get_token
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Accepts a token if present. If not, it's still ok.
|
163
|
+
#
|
164
|
+
def accept_token()
|
165
|
+
logger.debug("Accepts a token")
|
166
|
+
begin
|
167
|
+
get_token()
|
168
|
+
rescue StandardError => e
|
169
|
+
logger.error("Suppressing error: #{e.message}")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Requires an admin session. All this means is that the session is
|
175
|
+
# issued for an admin user (role == 1000).
|
176
|
+
#
|
177
|
+
def require_admin_token
|
178
|
+
logger.debug("Requires an admin token")
|
179
|
+
get_token(required_role: Roles::ADMIN)
|
180
|
+
end
|
181
|
+
|
182
|
+
##
|
183
|
+
# Determines if the user is authorized for the object.
|
184
|
+
#
|
185
|
+
def authorized?(obj)
|
186
|
+
logger.debug("Checking to see if authorized to access object")
|
187
|
+
if !@auth_user
|
188
|
+
# :nocov:
|
189
|
+
return false
|
190
|
+
# :nocov:
|
191
|
+
elsif @auth_user.role >= Roles::ADMIN
|
192
|
+
return true
|
193
|
+
elsif obj.is_a? User
|
194
|
+
return obj == @auth_user
|
195
|
+
else
|
196
|
+
return obj.user == @auth_user
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require_dependency "rails_identity/application_controller"
|
2
|
+
|
3
|
+
module RailsIdentity
|
4
|
+
|
5
|
+
##
|
6
|
+
# This class is sessions controller that performs CRD on session objects.
|
7
|
+
# Note that a token includes its session ID. Use "current" to look up a
|
8
|
+
# session in the current context.
|
9
|
+
#
|
10
|
+
class SessionsController < ApplicationController
|
11
|
+
|
12
|
+
prepend_before_action :require_token, except: [:create, :options]
|
13
|
+
before_action :get_session, only: [:show, :destroy]
|
14
|
+
before_action :get_user, only: [:index]
|
15
|
+
|
16
|
+
##
|
17
|
+
# Lists all sessions that belong to the specified or authenticated user.
|
18
|
+
#
|
19
|
+
def index
|
20
|
+
@sessions = Session.where(user: @user)
|
21
|
+
expired = []
|
22
|
+
active = []
|
23
|
+
@sessions.each do |session|
|
24
|
+
if session.expired?
|
25
|
+
expired << session.uuid
|
26
|
+
else
|
27
|
+
active << session
|
28
|
+
end
|
29
|
+
end
|
30
|
+
SessionsCleanupJob.perform_later(*expired)
|
31
|
+
render json: active, except: [:secret]
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# This action is essentially the login action. Note that get_user is not
|
36
|
+
# triggered for this action because we will look at username first. That
|
37
|
+
# would be the "normal" way to login. The alternative would be with the
|
38
|
+
# token based authentication. If the latter doesn't make sense, just use
|
39
|
+
# the username and password approach.
|
40
|
+
#
|
41
|
+
def create
|
42
|
+
@user = User.find_by_username(session_params[:username])
|
43
|
+
if (@user && @user.authenticate(session_params[:password])) || get_user()
|
44
|
+
raise Errors::UnauthorizedError unless @user.verified
|
45
|
+
@session = Session.new(user: @user)
|
46
|
+
if @session.save
|
47
|
+
render json: @session, except: [:secret], status: 201
|
48
|
+
else
|
49
|
+
# :nocov:
|
50
|
+
render_errors 400, @session.full_error_messages
|
51
|
+
# :nocov:
|
52
|
+
end
|
53
|
+
else
|
54
|
+
render_error 401, "Invalid username or password"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Shows a session information.
|
60
|
+
#
|
61
|
+
def show
|
62
|
+
render json: @session, except: [:secret]
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Deletes a session.
|
67
|
+
#
|
68
|
+
def destroy
|
69
|
+
if @session.destroy
|
70
|
+
render body: "", status: 204
|
71
|
+
else
|
72
|
+
# :nocov:
|
73
|
+
render_error 500, "Something went wrong. Oops!"
|
74
|
+
# :nocov:
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
##
|
81
|
+
# Get the specified or current session.
|
82
|
+
#
|
83
|
+
# An Errors::ObjectNotFoundError is raised if the session does not
|
84
|
+
# exist (or deleted due to expiration).
|
85
|
+
#
|
86
|
+
# An Errors::UnauthorizedError is raised if the authenticated user
|
87
|
+
# does not have authorization for the specified session.
|
88
|
+
#
|
89
|
+
def get_session
|
90
|
+
session_id = params[:id]
|
91
|
+
if session_id == "current"
|
92
|
+
session_id = @auth_session.id
|
93
|
+
end
|
94
|
+
@session = find_object(Session, session_id)
|
95
|
+
if !authorized?(@session)
|
96
|
+
raise Errors::UnauthorizedError
|
97
|
+
elsif @session.expired?
|
98
|
+
@session.destroy
|
99
|
+
raise Errors::ObjectNotFoundError
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def session_params
|
104
|
+
params.permit(:username, :password)
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require_dependency "rails_identity/application_controller"
|
2
|
+
|
3
|
+
module RailsIdentity
|
4
|
+
|
5
|
+
##
|
6
|
+
# Users controller that performs CRUD on users.
|
7
|
+
#
|
8
|
+
class UsersController < ApplicationController
|
9
|
+
|
10
|
+
# All except user creation requires a session token. Note that reset
|
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]
|
15
|
+
|
16
|
+
# Some actions must have a user specified.
|
17
|
+
before_action :get_user, only: [:show, :destroy]
|
18
|
+
|
19
|
+
##
|
20
|
+
# List all users (but only works for admin user).
|
21
|
+
#
|
22
|
+
def index
|
23
|
+
@users = User.all
|
24
|
+
render json: @users, except: [:password_digest]
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Creates a new user. This action does not require any auth although it
|
29
|
+
# is optional.
|
30
|
+
#
|
31
|
+
def create
|
32
|
+
@user = User.new(user_params)
|
33
|
+
if @user.save
|
34
|
+
render json: @user, except: [:verification_token, :reset_token, :password_digest], status: 201
|
35
|
+
UserMailer.email_verification(@user).deliver_later
|
36
|
+
else
|
37
|
+
render_errors 400, @user.errors.full_messages
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Renders a user data.
|
43
|
+
#
|
44
|
+
def show
|
45
|
+
render json: @user, except: [:password_digest], methods: [:role]
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Patches the user. Some overloading operations here. There are five
|
50
|
+
# notable ways to update a user.
|
51
|
+
#
|
52
|
+
# - Issue a reset token
|
53
|
+
# If params has :issue_reset_token set to true, the action will
|
54
|
+
# issue a reset token for the user and returns 204. Yes, 204 No
|
55
|
+
# Content. TODO: in the future, the action will trigger an email.
|
56
|
+
# - Reset the password
|
57
|
+
# Two ways to reset password:
|
58
|
+
# - Provide the old password along with the new password and
|
59
|
+
# confirmation.
|
60
|
+
# - Provide the reset token as the auth token.
|
61
|
+
# - Issue a verification token
|
62
|
+
# - Change other data
|
63
|
+
#
|
64
|
+
def update
|
65
|
+
if params[:issue_reset_token] || params[:issue_verification_token]
|
66
|
+
# For issuing a reset token, one does not need an auth token. so do
|
67
|
+
# not authorize the request.
|
68
|
+
raise Errors::UnauthorizedError unless params[:id] == "current"
|
69
|
+
get_user_for_token()
|
70
|
+
raise Errors::UnauthorizedError unless params[:username] == @user.username
|
71
|
+
if params[:issue_reset_token]
|
72
|
+
update_token(:reset_token)
|
73
|
+
else
|
74
|
+
update_token(:verification_token)
|
75
|
+
end
|
76
|
+
else
|
77
|
+
get_user()
|
78
|
+
if params[:password]
|
79
|
+
if params[:old_password]
|
80
|
+
raise Errors::UnauthorizedError unless @user.authenticate(params[:old_password])
|
81
|
+
else
|
82
|
+
raise Errors::UnauthorizedError unless @token == @user.reset_token
|
83
|
+
end
|
84
|
+
end
|
85
|
+
update_user(user_params)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Deletes a user.
|
91
|
+
#
|
92
|
+
def destroy
|
93
|
+
if @user.destroy
|
94
|
+
render body: '', status: 204
|
95
|
+
else
|
96
|
+
# :nocov:
|
97
|
+
render_error 500, "Something went wrong!"
|
98
|
+
# :nocov:
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
##
|
105
|
+
# This method normally updates the user using permitted params.
|
106
|
+
#
|
107
|
+
def update_user(update_user_params)
|
108
|
+
if @user.update_attributes(update_user_params)
|
109
|
+
render json: @user, except: [:password_digest]
|
110
|
+
else
|
111
|
+
render_errors 400, @user.errors.full_messages
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# This method updates user with a new reset token. Only used for this
|
117
|
+
# operation.
|
118
|
+
#
|
119
|
+
def update_token(kind)
|
120
|
+
@user.issue_token(kind)
|
121
|
+
@user.save
|
122
|
+
if kind == :reset_token
|
123
|
+
UserMailer.password_reset(@user).deliver_later
|
124
|
+
else
|
125
|
+
UserMailer.email_verification(@user).deliver_later
|
126
|
+
end
|
127
|
+
render body: '', status: 204
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
##
|
133
|
+
# This overrides the application controller's get_user method. Since
|
134
|
+
# resource object of this users controller is user, the id is
|
135
|
+
# specified in :id param.
|
136
|
+
#
|
137
|
+
def get_user
|
138
|
+
if params[:id] == "current"
|
139
|
+
raise Errors::UnauthorizedError if @auth_user.nil?
|
140
|
+
params[:id] = @auth_user.uuid
|
141
|
+
end
|
142
|
+
@user = find_object(User, params[:id])
|
143
|
+
raise Errors::UnauthorizedError unless authorized?(@user)
|
144
|
+
return @user
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# For issuing a new reset or for re-issuing a verification token, use
|
149
|
+
# this method to get user.
|
150
|
+
#
|
151
|
+
def get_user_for_token
|
152
|
+
@user = User.find_by_username(params[:username])
|
153
|
+
raise Errors::ObjectNotFoundError if @user.nil?
|
154
|
+
return @user
|
155
|
+
end
|
156
|
+
|
157
|
+
def user_params
|
158
|
+
# Only ADMIN can assign the attribute role. The attribute value will
|
159
|
+
# be ignored if the user is not an ADMIN.
|
160
|
+
if @auth_user.try(:role).try(:>=, Roles::ADMIN)
|
161
|
+
params.permit(:username, :password, :password_confirmation, :role, :verified)
|
162
|
+
else
|
163
|
+
params.permit(:username, :password, :password_confirmation, :verified)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RailsIdentity
|
2
|
+
module ApplicationHelper
|
3
|
+
|
4
|
+
##
|
5
|
+
# Renders a single error.
|
6
|
+
#
|
7
|
+
def render_error(status, msg)
|
8
|
+
render json: {errors: [msg]}, status: status
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Renders multiple errors
|
13
|
+
#
|
14
|
+
def render_errors(status, msgs)
|
15
|
+
render json: {errors: msgs}, status: status
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RailsIdentity
|
2
|
+
class UserMailer < ApplicationMailer
|
3
|
+
|
4
|
+
def email_verification(user)
|
5
|
+
@user = user
|
6
|
+
mail(to: @user.username, subject: "[rails-identity] Email Confirmation")
|
7
|
+
end
|
8
|
+
|
9
|
+
def password_reset(user)
|
10
|
+
@user = user
|
11
|
+
mail(to: @user.username, subject: "[rails-identity] Password Reset")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|