trainmaster 0.1.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.
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