token_master 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +1166 -0
- data/README.md +58 -99
- data/dummy/.gitignore +18 -0
- data/dummy/Gemfile +30 -0
- data/dummy/Rakefile +6 -0
- data/dummy/app/assets/config/manifest.js +3 -0
- data/dummy/app/assets/images/.keep +0 -0
- data/dummy/app/assets/javascripts/application.js +16 -0
- data/dummy/app/assets/javascripts/cable.js +13 -0
- data/dummy/app/assets/javascripts/channels/.keep +0 -0
- data/dummy/app/assets/stylesheets/application.css +15 -0
- data/dummy/app/channels/application_cable/channel.rb +4 -0
- data/dummy/app/channels/application_cable/connection.rb +4 -0
- data/dummy/app/controllers/application_controller.rb +3 -0
- data/dummy/app/controllers/concerns/.keep +0 -0
- data/dummy/app/helpers/application_helper.rb +2 -0
- data/dummy/app/jobs/application_job.rb +2 -0
- data/dummy/app/mailers/application_mailer.rb +4 -0
- data/dummy/app/models/application_record.rb +3 -0
- data/dummy/app/models/concerns/.keep +0 -0
- data/dummy/app/models/user.rb +10 -0
- data/dummy/app/views/layouts/application.html.erb +14 -0
- data/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/dummy/bin/bundle +3 -0
- data/dummy/bin/rails +9 -0
- data/dummy/bin/rake +9 -0
- data/dummy/bin/setup +34 -0
- data/dummy/bin/spring +17 -0
- data/dummy/bin/update +29 -0
- data/dummy/config.ru +5 -0
- data/dummy/config/application.rb +15 -0
- data/dummy/config/boot.rb +3 -0
- data/dummy/config/cable.yml +9 -0
- data/dummy/config/database.yml +14 -0
- data/dummy/config/environment.rb +6 -0
- data/dummy/config/environments/development.rb +54 -0
- data/dummy/config/environments/test.rb +42 -0
- data/dummy/config/initializers/assets.rb +11 -0
- data/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/dummy/config/initializers/session_store.rb +3 -0
- data/dummy/config/initializers/token_master.rb +29 -0
- data/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/dummy/config/locales/en.yml +23 -0
- data/dummy/config/puma.rb +47 -0
- data/dummy/config/routes.rb +3 -0
- data/dummy/config/secrets.yml +9 -0
- data/dummy/config/spring.rb +6 -0
- data/dummy/db/migrate/20170505170857_create_users.rb +11 -0
- data/dummy/db/migrate/20170505171217_add_confirm_tokenable_to_users.rb +26 -0
- data/dummy/db/schema.rb +41 -0
- data/dummy/db/seeds.rb +14 -0
- data/dummy/spec/factories/users.rb +8 -0
- data/dummy/spec/models/user_spec.rb +12 -0
- data/dummy/spec/rails_helper.rb +54 -0
- data/dummy/spec/spec_helper.rb +85 -0
- data/dummy/spec/support/factory_bot.rb +3 -0
- data/dummy/spec/support/shoulda_matchers.rb +6 -0
- data/lib/token_master/core.rb +14 -0
- data/lib/token_master/model.rb +6 -1
- data/lib/token_master/version.rb +1 -1
- metadata +62 -3
data/README.md
CHANGED
@@ -9,48 +9,17 @@
|
|
9
9
|
[![Test Coverage](https://codeclimate.com/github/LaunchPadLab/token-master/badges/coverage.svg)](https://codeclimate.com/github/LaunchPadLab/token-master/coverage)
|
10
10
|
[![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
* [Motivation](#motivation)
|
15
|
-
* [Token Master](#enter-the-token-master)
|
16
|
-
* [Quick Start](#quick-start)
|
17
|
-
* [Details](#details)
|
18
|
-
* [FAQ](#faq)
|
19
|
-
* [Comparisons](#comparisons)
|
20
|
-
|
21
|
-
## Motivation
|
22
|
-
Whenever your application manages users, you will inevitably need to handle email confirmation, password reset, user invitations, and other authentication flows. While not too complicated, they are sort of annoying to implement and some great libraries have our backs. [Devise][devise] and [Sorcery][sorcery] are great options that we have used in the past, but we found ourselves wanting both a little less and a little more. See our more detailed thoughts on these options [below](#comparisons).
|
23
|
-
|
24
|
-
### User Authentication Flows
|
25
|
-
Email confirmation, password reset, user invitations are all variations of the same process:
|
26
|
-
|
27
|
-
1. Create a unique token that allows the user temporary and limited access to your application
|
28
|
-
2. Notify the user with a link to redeem the token
|
29
|
-
3. Redeem or reject the token based on certain conditions (ex. validity, expiration, etc)
|
30
|
-
4. Update the user with any new information
|
31
|
-
5. Revoke the token
|
32
|
-
|
33
|
-
They are all *tokenable* activities, and all you need to do them is a **Token Master**!
|
34
|
-
|
35
|
-
## Enter the Token Master
|
36
|
-
|
37
|
-
### Front-end agnostic
|
12
|
+
Simple token logic for providing (temporary) restricted access.
|
38
13
|
No routing, views, controllers, or mailers, just logic that you can use wherever and whenever you want.
|
39
14
|
|
40
|
-
|
41
|
-
Token Master does not handle user authentication, it assumes you have this covered with `has_secure_password`, Devise, Sorcery, or other solutions
|
42
|
-
|
43
|
-
### Unobtrusive
|
44
|
-
Does not take over your app, minimal magic, and only if you want it. Token Master works with your existing authentication solution.
|
45
|
-
|
46
|
-
### Flexible
|
47
|
-
Works for APIs, ERB apps and everything in between.
|
15
|
+
Tokens can be used for any action that needs the access, such as inviting, confirming, or resetting passwords. These actions can be considered *tokenable actions*.
|
48
16
|
|
49
|
-
|
50
|
-
Only 5 methods and you may not even use them all!
|
17
|
+
Tokenable actions can be attributed to any model, not just users. These models then become *tokenable models*.
|
51
18
|
|
52
|
-
|
53
|
-
|
19
|
+
* [Quick Start](#quick-start)
|
20
|
+
* [Details](#details)
|
21
|
+
* [FAQ](#faq)
|
22
|
+
* [Motivation](#motivation)
|
54
23
|
|
55
24
|
## Quick Start
|
56
25
|
|
@@ -67,19 +36,19 @@ Or install it yourself as:
|
|
67
36
|
|
68
37
|
`$ gem install token_master`
|
69
38
|
|
70
|
-
###
|
39
|
+
### Usage
|
71
40
|
|
72
|
-
##### These examples assume Rails 5, but anything >= 4 will work
|
41
|
+
##### These examples assume Rails 5, but anything >= 4 will work
|
73
42
|
|
74
|
-
Let's say you want to add email confirmation flow to your User.
|
43
|
+
Let's say you want to add email confirmation flow to your User. Your **tokenable model** then is the **User** model, and the **tokenable action** might be something like *confirm* (although you can name it anything, as long as you are consistent).
|
75
44
|
|
76
|
-
1. Create and run a migration to add the necessary columns to the `users` table
|
45
|
+
1. Create and run a migration to add the necessary columns to the `users` table like so:
|
77
46
|
```
|
78
47
|
bundle exec rails generate token_master User confirm
|
79
48
|
bundle exec rails db:migrate
|
80
49
|
```
|
81
50
|
|
82
|
-
2. Add Token Master to the User class
|
51
|
+
2. Add the Token Master `token_master` hook to the User class, and pass in the symbol for your *tokenable action*:
|
83
52
|
|
84
53
|
```
|
85
54
|
class User < ApplicationRecord
|
@@ -87,13 +56,11 @@ class User < ApplicationRecord
|
|
87
56
|
end
|
88
57
|
```
|
89
58
|
|
90
|
-
3. Somewhere during the signup flow, generate and send the token
|
59
|
+
3. Somewhere during the signup flow, generate and send the token:
|
91
60
|
|
92
61
|
```
|
93
62
|
class UsersController < ApplicationController
|
94
63
|
|
95
|
-
...
|
96
|
-
|
97
64
|
def create
|
98
65
|
|
99
66
|
# Creating the user is up to you, here is an example
|
@@ -103,39 +70,40 @@ class UsersController < ApplicationController
|
|
103
70
|
password_confirmation: params[:password_confirmation]
|
104
71
|
)
|
105
72
|
|
106
|
-
# Generate and save a unique token
|
73
|
+
# Generate and save a unique token on the new user
|
107
74
|
token = user.set_confirm_token!
|
108
75
|
|
109
76
|
# Mark the token as sent
|
110
77
|
user.send_confirm_instructions! do
|
111
|
-
# Sending the email is up to you
|
78
|
+
# Sending the email is up to you, by passing a block here:
|
112
79
|
UserMailer.send_confirm(user) # or some other logic
|
113
80
|
end
|
114
81
|
end
|
115
82
|
|
116
|
-
|
83
|
+
def resend_confirmation_instructions
|
84
|
+
|
85
|
+
# if you have a 'resend instructions?' flow you can generate a new token and send instructions again in one step
|
86
|
+
user.resend_confirm_instructions! do
|
87
|
+
# Sending the email is up to you, by passing a block here:
|
88
|
+
UserMailer.send_confirm(user) # or some other logic
|
89
|
+
end
|
90
|
+
end
|
117
91
|
|
118
92
|
end
|
119
93
|
```
|
120
94
|
|
121
|
-
4. Somewhere during the confirmation flow, find and confirm the
|
95
|
+
4. Somewhere during the confirmation flow, find and confirm the user:
|
122
96
|
|
123
97
|
```
|
124
98
|
class UsersController < ApplicationController
|
125
|
-
|
126
|
-
...
|
127
|
-
|
128
99
|
def confirm
|
129
100
|
|
130
|
-
#
|
101
|
+
# finds the user by the token, and mark the token as completed
|
131
102
|
user = User.confirm_by_token!(params[:token])
|
132
103
|
|
133
104
|
...
|
134
105
|
|
135
106
|
end
|
136
|
-
|
137
|
-
...
|
138
|
-
|
139
107
|
end
|
140
108
|
```
|
141
109
|
|
@@ -148,16 +116,16 @@ When you ran the generator
|
|
148
116
|
```
|
149
117
|
bundle exec rails generate token_master User confirm
|
150
118
|
```
|
151
|
-
you provided
|
152
|
-
* `User` - The class name of the model to which you are adding the *tokenable*
|
153
|
-
* `confirm` - The name of the *tokenable*
|
119
|
+
you provided two arguments:
|
120
|
+
* `User` - The class name of the model to which you are adding the *tokenable action*
|
121
|
+
* `confirm` - The name of the *tokenable action*
|
154
122
|
|
155
123
|
Both of these could be anything, as long as you use the same class and name later on. If you like, you can create multiple *tokenables* at the same time, just add more space-separated *tokenable* names when calling the generator:
|
156
124
|
```
|
157
|
-
bundle exec rails generate token_master User confirm invite reset
|
125
|
+
bundle exec rails generate token_master User confirm invite reset
|
158
126
|
```
|
159
127
|
|
160
|
-
Running the generator does
|
128
|
+
Running the generator does two things:
|
161
129
|
1. Creates a migration file in `#{Rails.root}/db/migrate` that looks like:
|
162
130
|
|
163
131
|
```
|
@@ -182,22 +150,21 @@ TokenMaster.config do |config|
|
|
182
150
|
# Set up your configurations for each *tokenable* using the methods at the bottom of this file.
|
183
151
|
# Example: For `confirm` logic:
|
184
152
|
#
|
185
|
-
# config.add_tokenable_options :confirm,
|
186
|
-
# token_lifetime: 15, # days
|
187
|
-
# required_params: [:email],
|
188
|
-
# token_length: 30 # characters
|
189
|
-
#
|
190
153
|
# Default values:
|
191
154
|
# token_lifetime = 15 # days
|
192
155
|
# required_params = []
|
193
156
|
# token_length = 20 # characters
|
194
|
-
|
157
|
+
|
158
|
+
config.add_tokenable_options :confirm,
|
159
|
+
token_lifetime: 15, # days
|
160
|
+
required_params: [:email],
|
161
|
+
token_length: 30 # characters
|
195
162
|
end
|
196
163
|
```
|
197
|
-
The default values will be used unless you configure them otherwise. These options can be set for each *tokenable*.
|
164
|
+
The default values will be used unless you configure them otherwise. These options can be set for each *tokenable action*.
|
198
165
|
|
199
166
|
### The Model
|
200
|
-
When you added the *tokenable* to your model
|
167
|
+
When you added the Token Master hook and *tokenable action* to your model
|
201
168
|
```
|
202
169
|
class User < ApplicationRecord
|
203
170
|
token_master :confirm
|
@@ -207,57 +174,58 @@ just make sure the class `User` and *tokenable(s)* `:confirm` (this can be multi
|
|
207
174
|
|
208
175
|
Ex.
|
209
176
|
```
|
210
|
-
token_master :confirm, :invite, :reset
|
177
|
+
token_master :confirm, :invite, :reset
|
211
178
|
```
|
212
179
|
|
213
|
-
|
180
|
+
1. The `token_master` hook is included automatically by Token Master in your `ApplicationRecord` base class.
|
214
181
|
|
215
|
-
|
182
|
+
However, if necessary, you can add this yourself by including the following in your class:
|
216
183
|
```
|
217
184
|
include TokenMaster::Model
|
218
185
|
```
|
219
|
-
This adds the `token_master` class method we used above.
|
186
|
+
This adds the `token_master` class method we used above, and you can make the same calls we described in the `confirm` example above.
|
187
|
+
|
188
|
+
2. When you call the `token_master` class method, for each *tokenable action* you provide, a handful of methods are added to the class for each *tokenable action*, and named accordingly.
|
220
189
|
|
221
|
-
|
190
|
+
Assuming the *tokenable action* below is `confirm`, the methods would look like this:
|
222
191
|
|
223
192
|
Instance methods
|
224
193
|
* `set_confirm_token!`
|
225
194
|
* `send_confirm_instructions!`
|
195
|
+
* `resend_confirm_instructions!`
|
226
196
|
* `confirm_status`
|
227
197
|
* `force_confirm!`
|
228
198
|
|
229
199
|
Class methods
|
230
200
|
* `confirm_by_token!`
|
231
201
|
|
232
|
-
In addition to the
|
202
|
+
In addition to the three you have already seen in action, there is also:
|
233
203
|
|
234
|
-
`confirm_status` - returns the current status of the *tokenable*. This is one of:
|
204
|
+
`confirm_status` - returns the current status of the *tokenable action*. This is one of:
|
235
205
|
* 'no token'
|
236
206
|
* 'created'
|
237
207
|
* 'sent'
|
238
208
|
* 'completed'
|
239
209
|
* 'expired'
|
240
210
|
|
241
|
-
`force_confirm!` - forcibly completes the given *tokenable*
|
211
|
+
`force_confirm!` - forcibly completes the given *tokenable action*
|
242
212
|
|
243
213
|
See the [Api Docs][docs] for more details.
|
244
214
|
|
245
215
|
## Advanced
|
246
|
-
Sometimes in order to redeem a token, we want to make sure some additional information is present and possibly save that to our model.
|
216
|
+
Sometimes in order to redeem a token, we want to make sure some additional information is present and possibly save that to our model.
|
217
|
+
For example, when implementing a password reset flow, we want to update the User with the new password and make sure it's valid.
|
247
218
|
|
248
219
|
Assuming we are using `has_secure_password` or something similar all we need to do is:
|
249
|
-
1. Configure the *tokenable* to require these fields when redeeming the token
|
250
|
-
```
|
251
|
-
# in ../initializers/token_master.rb
|
220
|
+
1. Configure the *tokenable action* to require these fields when redeeming the token
|
252
221
|
|
222
|
+
**../initializers/token_master.rb**
|
223
|
+
```
|
253
224
|
TokenMaster.config do |config|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
...
|
260
|
-
|
225
|
+
config.add_tokenable_options :reset_password,
|
226
|
+
token_lifetime: 1
|
227
|
+
required_params: [:password, :password_confirmation]
|
228
|
+
token_length: 30
|
261
229
|
end
|
262
230
|
```
|
263
231
|
|
@@ -280,7 +248,7 @@ Yes! However, there is a small dependency on ActiveRecord, see below.
|
|
280
248
|
### Can I use this without ActiveRecord?
|
281
249
|
Almost! There is only a slight dependence on a few ActiveRecord methods and its on our radar to refactor this a bit. In the meantime, a workaround is to make sure the class you are using implements `update`, `update!`, `save`, and `find_by`. In addition, you have to either add Token Master to your class with `include TokenMaster::Model` or use the Token Master core module explicitly:
|
282
250
|
|
283
|
-
`
|
251
|
+
`TokenMaster::Core.set_token!(User, :confirm)` (which is equivalent to `user.set_confirm_token!(token)`)
|
284
252
|
|
285
253
|
See the [Api Docs][docs] for more details.
|
286
254
|
|
@@ -296,15 +264,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/Launch
|
|
296
264
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
297
265
|
|
298
266
|
---------------------------
|
299
|
-
|
300
|
-
## Comparisons
|
301
|
-
|
302
|
-
### Devise
|
303
|
-
[Devise][devise] is an amazing gem! It is perfect when you want an all-in-one solution that handles user authentication and associated flows for your Rails/ERB app. Everything is in the box, including the routes, controllers, views, and even mailers to handle user auth. But we often use Rails as an API and/or wanted more control over all those pieces and it became difficult to peel back all the layers to just to confirm a user's email.
|
304
|
-
|
305
|
-
### Sorcery
|
306
|
-
[Sorcery][sorcery] is great and we highly recommend it. It is closer to what we wanted but still was a bit more than we needed and even the < 20 methods seemed like more than necessary.
|
307
|
-
|
308
267
|
<!-- Links -->
|
309
268
|
[devise]: https://github.com/plataformatec/devise
|
310
269
|
[sorcery]: https://github.com/Sorcery/sorcery
|
data/dummy/.gitignore
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore all logfiles and tempfiles.
|
11
|
+
/log/*
|
12
|
+
/tmp/*
|
13
|
+
!/log/.keep
|
14
|
+
!/tmp/.keep
|
15
|
+
|
16
|
+
# Ignore Byebug command history file.
|
17
|
+
.byebug_history
|
18
|
+
.DS_Store
|
data/dummy/Gemfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
git_source(:github) do |repo_name|
|
4
|
+
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
|
5
|
+
"https://github.com/#{repo_name}.git"
|
6
|
+
end
|
7
|
+
|
8
|
+
gem 'bcrypt', '~> 3.1.7'
|
9
|
+
gem 'factory_bot_rails'
|
10
|
+
gem 'faker'
|
11
|
+
gem 'pg', '~> 0.18'
|
12
|
+
# gem 'puma', '~> 3.0'
|
13
|
+
gem 'rails', '~> 5.0.2'
|
14
|
+
gem 'token_master', path: './../..'
|
15
|
+
|
16
|
+
group :development, :test do
|
17
|
+
gem 'byebug', platform: :mri
|
18
|
+
gem 'pry'
|
19
|
+
gem 'rspec-rails'
|
20
|
+
end
|
21
|
+
|
22
|
+
group :development do
|
23
|
+
gem 'web-console', '>= 3.3.0'
|
24
|
+
gem 'listen'
|
25
|
+
end
|
26
|
+
|
27
|
+
group :test do
|
28
|
+
gem 'shoulda-matchers'
|
29
|
+
gem 'rspec-collection_matchers'
|
30
|
+
end
|
data/dummy/Rakefile
ADDED
File without changes
|
@@ -0,0 +1,16 @@
|
|
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. JavaScript code in this file should be added after the last require_* statement.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require jquery
|
14
|
+
//= require jquery_ujs
|
15
|
+
//= require turbolinks
|
16
|
+
//= require_tree .
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// Action Cable provides the framework to deal with WebSockets in Rails.
|
2
|
+
// You can generate new channels where WebSocket features live using the rails generate channel command.
|
3
|
+
//
|
4
|
+
//= require action_cable
|
5
|
+
//= require_self
|
6
|
+
//= require_tree ./channels
|
7
|
+
|
8
|
+
(function() {
|
9
|
+
this.App || (this.App = {});
|
10
|
+
|
11
|
+
App.cable = ActionCable.createConsumer();
|
12
|
+
|
13
|
+
}).call(this);
|
File without changes
|
@@ -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 other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
File without changes
|