trainmaster 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +286 -0
  4. data/Rakefile +38 -0
  5. data/app/controllers/trainmaster/application_controller.rb +9 -0
  6. data/app/controllers/trainmaster/sessions_controller.rb +141 -0
  7. data/app/controllers/trainmaster/users_controller.rb +199 -0
  8. data/app/helpers/trainmaster/application_helper.rb +313 -0
  9. data/app/helpers/trainmaster/sessions_helper.rb +4 -0
  10. data/app/helpers/trainmaster/users_helper.rb +4 -0
  11. data/app/jobs/trainmaster/sessions_cleanup_job.rb +13 -0
  12. data/app/mailers/application_mailer.rb +4 -0
  13. data/app/mailers/trainmaster/user_mailer.rb +14 -0
  14. data/app/models/trainmaster/session.rb +56 -0
  15. data/app/models/trainmaster/user.rb +77 -0
  16. data/app/views/layouts/mailer.html.erb +5 -0
  17. data/app/views/layouts/mailer.text.erb +1 -0
  18. data/app/views/layouts/trainmaster/application.html.erb +14 -0
  19. data/app/views/trainmaster/user_mailer/email_verification.html.erb +12 -0
  20. data/app/views/trainmaster/user_mailer/email_verification.text.erb +13 -0
  21. data/app/views/trainmaster/user_mailer/password_reset.html.erb +14 -0
  22. data/app/views/trainmaster/user_mailer/password_reset.text.erb +15 -0
  23. data/config/routes.rb +10 -0
  24. data/db/migrate/20161120020344_create_trainmaster_users.rb +23 -0
  25. data/db/migrate/20161120020722_create_trainmaster_sessions.rb +11 -0
  26. data/lib/tasks/trainmaster_tasks.rake +4 -0
  27. data/lib/trainmaster.rb +10 -0
  28. data/lib/trainmaster/cache.rb +28 -0
  29. data/lib/trainmaster/engine.rb +9 -0
  30. data/lib/trainmaster/roles.rb +12 -0
  31. data/lib/trainmaster/version.rb +3 -0
  32. data/test/controllers/trainmaster/application_controller_test.rb +106 -0
  33. data/test/controllers/trainmaster/sessions_controller_test.rb +275 -0
  34. data/test/controllers/trainmaster/users_controller_test.rb +335 -0
  35. data/test/dummy/README.rdoc +28 -0
  36. data/test/dummy/Rakefile +6 -0
  37. data/test/dummy/app/assets/javascripts/application.js +13 -0
  38. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  39. data/test/dummy/app/controllers/application_controller.rb +5 -0
  40. data/test/dummy/app/helpers/application_helper.rb +2 -0
  41. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  42. data/test/dummy/bin/bundle +3 -0
  43. data/test/dummy/bin/rails +4 -0
  44. data/test/dummy/bin/rake +4 -0
  45. data/test/dummy/bin/setup +29 -0
  46. data/test/dummy/config.ru +4 -0
  47. data/test/dummy/config/application.rb +34 -0
  48. data/test/dummy/config/boot.rb +5 -0
  49. data/test/dummy/config/database.yml +25 -0
  50. data/test/dummy/config/environment.rb +5 -0
  51. data/test/dummy/config/environments/development.rb +41 -0
  52. data/test/dummy/config/environments/production.rb +79 -0
  53. data/test/dummy/config/environments/test.rb +44 -0
  54. data/test/dummy/config/initializers/assets.rb +11 -0
  55. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  56. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  57. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  58. data/test/dummy/config/initializers/inflections.rb +16 -0
  59. data/test/dummy/config/initializers/mime_types.rb +4 -0
  60. data/test/dummy/config/initializers/session_store.rb +3 -0
  61. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  62. data/test/dummy/config/locales/en.yml +23 -0
  63. data/test/dummy/config/routes.rb +4 -0
  64. data/test/dummy/config/secrets.yml +22 -0
  65. data/test/dummy/public/404.html +67 -0
  66. data/test/dummy/public/422.html +67 -0
  67. data/test/dummy/public/500.html +66 -0
  68. data/test/dummy/public/favicon.ico +0 -0
  69. data/test/fixtures/trainmaster/sessions.yml +36 -0
  70. data/test/fixtures/trainmaster/users.yml +27 -0
  71. data/test/integration/navigation_test.rb +10 -0
  72. data/test/jobs/trainmaster/sessions_cleanup_job_test.rb +9 -0
  73. data/test/mailers/previews/trainmaster/user_mailer_preview.rb +6 -0
  74. data/test/mailers/trainmaster/user_mailer_test.rb +9 -0
  75. data/test/models/trainmaster/session_test.rb +26 -0
  76. data/test/models/trainmaster/user_test.rb +52 -0
  77. data/test/test_helper.rb +33 -0
  78. data/test/trainmaster.rb +12 -0
  79. metadata +327 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7510a9194b744aa809f3e63ab7df28e5d115cc2f
4
+ data.tar.gz: a7a7bba317744d1311eb97d0df63fcfdac44defb
5
+ SHA512:
6
+ metadata.gz: bbb59e5e424cff10717d6f9a59a8f53026efffdf450690641b3f58f6b9a1c91c1fa951ae9207fbf138242abece14a2dda934dfd24da57edd772233161c6cb078
7
+ data.tar.gz: 2688b0cf496fc8087696395f8d4dd12cc52bdc27494e1237f539ec243c4874c3de05445eb38c96a6f20313cdf4500b1cf680729c2c16d71639cdbdd13433d33f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 David An
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,286 @@
1
+ # trainmaster
2
+
3
+ [![Build Status](https://travis-ci.org/davidan1981/trainmaster.svg?branch=master)](https://travis-ci.org/davidan1981/trainmaster)
4
+ [![Coverage Status](https://coveralls.io/repos/github/davidan1981/trainmaster/badge.svg?branch=master)](https://coveralls.io/github/davidan1981/trainmaster?branch=master)
5
+ [![Code Climate](https://codeclimate.com/github/davidan1981/trainmaster/badges/gpa.svg)](https://codeclimate.com/github/davidan1981/trainmaster)
6
+ [![Gem Version](https://badge.fury.io/rb/trainmaster.svg)](https://badge.fury.io/rb/trainmaster)
7
+
8
+ trainmaster is a Rails engine that provides
9
+ a [JWT](https://jwt.io/)-based session management platform for API
10
+ development. This plugin is suitable for developing RESTful APIs that do
11
+ not require an enterprise identity service. No cookies or non-unique IDs
12
+ involved in this project.
13
+
14
+ It is a continuation of
15
+ [rails-identity](https://github.com/davidan1981/rails-identity) which has
16
+ been deprecated due to backwards compatibility issues.
17
+
18
+ This documentation uses [httpie](https://github.zom/) (rather than curl)
19
+ to demonstrate making HTTP requests from the command line.
20
+
21
+ ## Features
22
+
23
+ * Mountable Rails engine
24
+ * JWT-based session management API (REST)
25
+ * API key based authentication
26
+ * Email verification workflow
27
+ * Password reset workflow
28
+ * Caching
29
+ * OAuth authentication (beta)
30
+
31
+ ## Install
32
+
33
+ Install the gem, or
34
+ go to your app's directory and add this line to your `Gemfile`:
35
+
36
+ ```ruby
37
+ gem 'trainmaster'
38
+ ```
39
+
40
+ Then, add the following line in `application.rb`:
41
+
42
+ ```ruby
43
+ require 'trainmaster'
44
+ ```
45
+
46
+ And the following in `route.rb`:
47
+
48
+ ```ruby
49
+ require 'trainmaster'
50
+
51
+ Rails.application.routes.draw do
52
+ mount Trainmaster::Engine, at: "/"
53
+ end
54
+ ```
55
+
56
+ Note that you may designate a different target prefix other than the root.
57
+ Then, run `bundle install` and do `rake routes` to verify the routes.
58
+
59
+ Next, install migrations from trainmaster and perform migrations:
60
+
61
+ $ bundle exec rake trainmaster:install:migrations
62
+ $ bundle exec rake db:migrate RAILS_ENV=development
63
+
64
+ FYI, to see all `rake` tasks, do the following:
65
+
66
+ $ bundle exec rake --tasks
67
+
68
+ ### Other Plugins
69
+
70
+ trainmaster uses ActiveJob to perform tasks asynchronously, which
71
+ requires a back-end module. For example, you can use
72
+ [DelayedJob](https://github.com/collectiveidea/delayed_job) by adding the
73
+ following in Gemfile.
74
+
75
+ ```ruby
76
+ gem 'delayed_job_active_record'
77
+ gem 'daemons'
78
+ ```
79
+
80
+ Also, email service must be specified in your app for sending out
81
+ email verification token and password reset token. Note that the
82
+ default email template is not sufficient for real use.
83
+ You must define your own mailer action views to cater emails for
84
+ your need.
85
+
86
+ To use OAuth, you must configure two endpoints. First, specify
87
+ `oauth_landing_page_url` to the URL that will assign token (from query
88
+ string) to a temporary storage such as cookie.
89
+
90
+ config.oauth_landing_page_url = '/oauth_success'
91
+
92
+ Once OAuth callback is successful, the controller will response a redirect
93
+ (302) to the URL specified above with `token=<actual token>` as a query
94
+ string.
95
+
96
+ Second, set a route for oauth failure.
97
+
98
+ get 'auth/failure', redirect_to('/oauth_failure')
99
+
100
+ This page should simply display failed authentication.
101
+
102
+
103
+ ### Other Changes
104
+
105
+ `Trainmaster::User` model is a STI model. It means your app can inherit
106
+ from `Trainmaster::User` with additional attributes. All data will be
107
+ stored in `trainmaster_users` table. This is particularly useful if you
108
+ want to extend the model to meet your needs.
109
+
110
+ ```ruby
111
+ class User < Trainmaster::User
112
+ # more validations, attributes, methods, ...
113
+ end
114
+ ```
115
+
116
+ ### Running Your App
117
+
118
+ Now you're ready. Run the server to test:
119
+
120
+ $ bundle exec rails server
121
+
122
+ To allow DelayedJob tasks to run, do
123
+
124
+ $ RAILS_ENV=development bin/delayed_job start
125
+
126
+ ## Usage
127
+
128
+ ### Create User
129
+
130
+ Make a POST request on `/users` with `email`, `password`, and
131
+ `password_confirmation` in the JSON payload.
132
+
133
+ $ http POST localhost:3000/users email=foo@example.com password="supersecret" password_confirmation="supersecret"
134
+
135
+ The response should be 201 if successful.
136
+
137
+ HTTP/1.1 201 Created
138
+ {
139
+ "created_at": "2016-04-05T02:02:11.410Z",
140
+ "deleted_at": null,
141
+ "metadata": null,
142
+ "role": 10,
143
+ "updated_at": "2016-04-05T02:02:11.410Z",
144
+ "username": "foo@example.com",
145
+ "uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
146
+ "verified": false
147
+ }
148
+
149
+ This request will send an email verification token to the user's email.
150
+ The app should craft the linked page to use the verification token to
151
+ start a session and set `verified` to true by the following:
152
+
153
+ http PATCH localhost:3000/users/current verified=true token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOm51bGwsInNlc3Npb25fdXVpZCI6IjU5YTQwODRjLTAwNWMtMTFlNi1hN2ExLTZjNDAwOGE2ZmEyYSIsInJvbGUiOm51bGwsImlhdCI6MTQ2MDQzMDczMiwiZXhwIjoxNDYwNDM0MzMyfQ.rdi5JT5NzI9iuXjWfhXjYhc0xF-aoVAaAPWepgSUaH0
154
+
155
+ Note that `current` can be used when UUID is unknown but the token is
156
+ specified. Also note that, if user's `verified` is `false`, some endpoints
157
+ will reject the request.
158
+
159
+ ### Create Session
160
+
161
+ A proper way to create a session is to use username and password:
162
+
163
+ $ http POST localhost:3000/sessions username=foo@example.com password=supersecret
164
+
165
+ HTTP/1.1 201 Created
166
+ {
167
+ "created_at": "2016-04-05T02:04:22.465Z",
168
+ "metadata": null,
169
+ "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ",
170
+ "updated_at": "2016-04-05T02:04:22.465Z",
171
+ "user_uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
172
+ "uuid": "b6fadba4-fad2-11e5-8fc3-6c4008a6fa2a"
173
+ }
174
+
175
+ Notice this is essentially a login process for single-page apps. The client
176
+ app should store the value of `token` in either `localStore` or `cookie`.
177
+ (To allow cross-domain, you may want to use `cookie`.)
178
+
179
+ ### Delete Session
180
+
181
+ A session can be deleted via a DELETE method. This is essentially a logout
182
+ process.
183
+
184
+ $ http DELETE localhost:3000/sessions/b6fadba4-fad2-11e5-8fc3-6c4008a6fa2a token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ
185
+
186
+ HTTP/1.1 204 No Content
187
+
188
+ Make sure to remove the token from its storage. The old tokens will no
189
+ longer work.
190
+
191
+ ### Password Reset
192
+
193
+ Since trainmaster is a RESTful service itself, password reset is done via
194
+ a PATCH method on the user resource. But you must specify either the old
195
+ password or a reset token. To use the old password:
196
+
197
+ $ http PATCH localhost:3000/users/68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a old_password="supersecret" password="reallysecret" password_confirmation="reallysecret" token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiJiNmZhZGJhNC1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5ODIxODYyLCJleHAiOjE0NjEwMzE0NjJ9.B9Ld00JvHUZT37THrwFrHzUwxIx6s3UFPbVCCwYzRrQ
198
+
199
+ To use a reset token, you must issue one first:
200
+
201
+ $ http PATCH localhost:3000/users/current username=foo@example.com issue_reset_token=true
202
+
203
+ HTTP/1.1 204 No Content
204
+
205
+ User token will be sent to the user's email. In a real application, the email
206
+ would include a link to a _page_ with JavaScript code automatically making a
207
+ PATCH request to `/users/current?token=<reset_token>`.
208
+
209
+ Note that the response includes a JWT token that looks similar to a normal
210
+ session token. Well a surprise! It _is_ a session token but with a shorter life span (1
211
+ hour). So use it instead on the password reset request:
212
+
213
+ http PATCH localhost:3000/users/current password="reallysecret" password_confirmation="reallysecret" token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiIzYjI5ZGI4OC1mYjlhLTExZTUtODNhOC02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5OTA3NTU0LCJleHAiOjE0NTk5MTExNTR9.g4iosqm8dOVUL5ErtCggsNAOs4WQV2u-heAUPf145jg
214
+
215
+ HTTP/1.1 200 OK
216
+ {
217
+ "created_at": "2016-04-05T02:02:11.410Z",
218
+ "deleted_at": null,
219
+ "metadata": null,
220
+ "reset_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOiI2OGRkYmIzYS1mYWQyLTExZTUtOGZjMy02YzQwMDhhNmZhMmEiLCJzZXNzaW9uX3V1aWQiOiIzYjI5ZGI4OC1mYjlhLTExZTUtODNhOC02YzQwMDhhNmZhMmEiLCJyb2xlIjoxMCwiaWF0IjoxNDU5OTA3NTU0LCJleHAiOjE0NTk5MTExNTR9.g4iosqm8dOVUL5ErtCggsNAOs4WQV2u-heAUPf145jg",
221
+ "role": 10,
222
+ "updated_at": "2016-04-06T01:55:45.163Z",
223
+ "username": "foo@example.com",
224
+ "uuid": "68ddbb3a-fad2-11e5-8fc3-6c4008a6fa2a",
225
+ "verification_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX3V1aWQiOm51bGwsInNlc3Npb25fdXVpZCI6IjU5YTQwODRjLTAwNWMtMTFlNi1hN2ExLTZjNDAwOGE2ZmEyYSIsInJvbGUiOm51bGwsImlhdCI6MTQ2MDQzMDczMiwiZXhwIjoxNDYwNDM0MzMyfQ.rdi5JT5NzI9iuXjWfhXjYhc0xF-aoVAaAPWepgSUaH0",
226
+ "verified": true
227
+ }
228
+
229
+ The token used with the request _must_ match the reset token previously
230
+ issued for the user.
231
+
232
+ ### Authentication and Authorization
233
+
234
+ There are two ways to do general authentication: token or API key.
235
+
236
+ To authorize a request to an action, use provided callbacks. trainmaster
237
+ provides three controller callbacks for each approach:
238
+
239
+ * Token
240
+ * `require_token`
241
+ * `require_admin_token`
242
+ * `accept_token` - If a token is given, trainmaster will validate it.
243
+ * API key
244
+ * `require_api_key`
245
+ * `require_admin_api_key`
246
+ * `accept_api_key` - If an API key is given, trainmaster will validate it.
247
+ * Both
248
+ * `require_auth` - A token or an API key must be given.
249
+ * `require_admin_auth` - A token or an API key of an admin must be given.
250
+ * `accept_auth` - If either a token or an API key is given, trainmaster will validate it
251
+
252
+ To determine if the authenticated user has access to a specific resource
253
+ object, use `authorized?`. An example of a resource authorization callback
254
+ looks like the following:
255
+
256
+ ```ruby
257
+ def authorize_user_to_obj(obj)
258
+ unless authorized?(obj)
259
+ raise Repia::Errors::Unauthorized
260
+ end
261
+ end
262
+ ```
263
+
264
+ ### Other Notes
265
+
266
+ #### Instance Variables
267
+
268
+ `ApplicationHelper` module will define the following instance variables:
269
+
270
+ * `@auth_user` - the authenticated user object
271
+ * `@auth_session` - the authenticated session
272
+ * `@token` - the token that authenticated the current session
273
+ * `@user` - the context user, only available if `get_user` is called
274
+
275
+ Try not to overload these variables. You may use these variables to enforce
276
+ further access control. Note that `@auth_session` and `@token` will be
277
+ populated only if a token is used to authenticate.
278
+
279
+ #### Roles
280
+
281
+ For convenience, trainmaster pre-defined four roles:
282
+
283
+ * Owner (1000) - the owner of the app
284
+ * Admin (100) - the admin(s) of the app
285
+ * User (10) - the user(s) of the app
286
+ * Public (0) - the rest of the world
@@ -0,0 +1,38 @@
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 = 'Trainmaster'
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
+ t.warning = false
35
+ end
36
+
37
+
38
+ task default: :test
@@ -0,0 +1,9 @@
1
+ module Trainmaster
2
+
3
+ ##
4
+ # The root application controller class in trainmaster.
5
+ #
6
+ class ApplicationController < Repia::Controller::Base
7
+ include ApplicationHelper
8
+ end
9
+ end
@@ -0,0 +1,141 @@
1
+ require_dependency "trainmaster/application_controller"
2
+
3
+ module Trainmaster
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_auth, 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
+ # A ApplicationController::UNAUTHORIZED_ERROR is thrown if user is not
42
+ # verified.
43
+ #
44
+ def create
45
+
46
+ # See if OAuth is used first. When authenticated successfully, either
47
+ # the existing user will be found or a new user will be created.
48
+ # Failure will be redirected to this action but will not match this
49
+ # branch.
50
+ if (omniauth_hash = request.env["omniauth.auth"])
51
+ @user = User.from_omniauth_hash(omniauth_hash)
52
+
53
+ # Then see if the request already has authentication. Note that if the
54
+ # user does not have access to the specified session owner, 401 will
55
+ # be thrown.
56
+ elsif accept_auth
57
+ @user = @auth_user
58
+
59
+ # Otherwise, it's a normal login process. Use username and password to
60
+ # authenticate. The user must exist, the password must be vaild, and
61
+ # the email must have been verified.
62
+ else
63
+ @user = User.find_by_username(session_params[:username])
64
+ if (@user.nil? || !@user.authenticate(session_params[:password]) ||
65
+ !@user.verified)
66
+ raise ApplicationController::UNAUTHORIZED_ERROR
67
+ end
68
+ end
69
+
70
+ # Finally, create session regardless of the method and store it.
71
+ @session = Session.new(user: @user)
72
+ if @session.save
73
+ if omniauth_hash
74
+ # redirect_to the app page that accepts new session token
75
+ url = Rails.application.config.oauth_landing_page_url
76
+ url = "#{url}?token=#{@session.token}"
77
+ render inline: "", status: 302, location: url
78
+ else
79
+ render json: @session, except: [:secret], status: 201
80
+ end
81
+ else
82
+ # :nocov:
83
+ render_errors 400, @session.full_error_messages
84
+ # :nocov:
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Shows a session information.
90
+ #
91
+ def show
92
+ render json: @session, except: [:secret]
93
+ end
94
+
95
+ ##
96
+ # Deletes a session.
97
+ #
98
+ def destroy
99
+ if @session.destroy
100
+ render body: "", status: 204
101
+ else
102
+ # :nocov:
103
+ render_error 400, @session.errors.full_messages
104
+ # :nocov:
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ ##
111
+ # Get the specified or current session.
112
+ #
113
+ # A Repia::Errors::NotFound is raised if the session does not
114
+ # exist (or deleted due to expiration).
115
+ #
116
+ # A ApplicationController::UNAUTHORIZED_ERROR is raised if the
117
+ # authenticated user does not have authorization for the specified
118
+ # session.
119
+ #
120
+ def get_session
121
+ session_id = params[:id]
122
+ if session_id == "current"
123
+ if @auth_session.nil?
124
+ raise Repia::Errors::NotFound
125
+ end
126
+ session_id = @auth_session.id
127
+ end
128
+ @session = find_object(Session, session_id)
129
+ authorize_for!(@session)
130
+ if @session.expired?
131
+ @session.destroy
132
+ raise Repia::Errors::NotFound
133
+ end
134
+ end
135
+
136
+ def session_params
137
+ params.permit(:username, :password)
138
+ end
139
+
140
+ end
141
+ end