rails-identity 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +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
|