token_master 0.0.1 → 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.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/.travis.yml +7 -0
- data/Gemfile +5 -0
- data/README.md +9 -0
- data/Rakefile +0 -5
- data/lib/token_master.rb +1 -1
- data/lib/token_master/config.rb +30 -0
- data/lib/token_master/core.rb +56 -26
- data/lib/token_master/error.rb +23 -27
- data/lib/token_master/model.rb +16 -9
- data/lib/token_master/version.rb +2 -1
- metadata +2 -3
- data/Gemfile.lock +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd04ff6bcd482acefe3cb6f02eef4cd186ef8722
|
4
|
+
data.tar.gz: 741a3a5d77632dd6736aaa7546d1aac10eafc69b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f5937cb91561db88615e0dcb47e772fa0f9751ba70914cf2e7fcd7dc5e5b10a3226a95241c1fd666f9de0597ec860244c7d069914e09236d95260de28283ff2
|
7
|
+
data.tar.gz: 53ae9e0eb252be8acf703999eccba66fa40ac3d647137fa4e21db444b14e9b13fa3bad293dd89fdd76dd16e4c5b9fad1cc1a0173702658ef52a21813462fa5cb
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
+
[](http://github.com/launchpadlab/token_master)
|
2
|
+
[](http://www.rubydoc.info/gems/token_master)
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/token_master)
|
1
5
|
[](https://travis-ci.org/LaunchPadLab/token-master)
|
6
|
+
[](https://codeclimate.com/github/LaunchPadLab/token-master/coverage)
|
7
|
+
[](#license)
|
2
8
|
|
3
9
|
# token-master
|
4
10
|
User management logic using tokens
|
@@ -68,3 +74,6 @@ config.add_tokenable_options :confirm, TokenMaster::Config::DEFAULT_VALUES
|
|
68
74
|
|
69
75
|
config.add_tokenable_options :reset, token_lifetime: 1, required_params: [:password, :password_confirmation], token_length: 15
|
70
76
|
```
|
77
|
+
|
78
|
+
## Api Documentation
|
79
|
+
[Rdoc](http://www.rubydoc.info/gems/token_master)
|
data/Rakefile
CHANGED
data/lib/token_master.rb
CHANGED
data/lib/token_master/config.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
module TokenMaster
|
2
|
+
# `TokenMaster::Config` manages the configuration options for tokenable actions. These can be set with the initializer provided with the generator. Default values will be used if options are not specified
|
2
3
|
class Config
|
4
|
+
|
5
|
+
# Provides default values for a tokenable action:
|
6
|
+
# * `token_lifetime` is an integer representing the number of days before the token expires
|
7
|
+
# * `required_params` is an array of symbols, e.g., `[:password, :password_confirmation]`
|
8
|
+
# * `token_length` is an integer representing the number of characters in the token
|
3
9
|
DEFAULT_VALUES = {
|
4
10
|
token_lifetime: 14,
|
5
11
|
required_params: [],
|
@@ -8,26 +14,50 @@ module TokenMaster
|
|
8
14
|
|
9
15
|
attr_accessor :options
|
10
16
|
|
17
|
+
# Creates a new instance of `TokenMaster::Config`
|
11
18
|
def initialize
|
12
19
|
@options = {}
|
13
20
|
end
|
14
21
|
|
22
|
+
# Sets the key-value pairs needed to complete a tokenable action<br /> Key-value pairs used to complete a tokenable action are the `token_lifetime`, `required_params` (can be blank), and `token_length`
|
23
|
+
# @example Set a Tokenable Option
|
24
|
+
# config.add_tokenable_options(:invite, { token_lifetime: 10, required_params: [:password, :password_confirmation], token_length: 12 }) #=>
|
25
|
+
# { invite: {
|
26
|
+
# token_lifetime: 10,
|
27
|
+
# required_params: [:password, :password_confirmation],
|
28
|
+
# token_length: 12
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
# @param [Symbol] key the tokenable action
|
32
|
+
# @param [Symbol=>[Integer, String, Array]] params the key-value pairs
|
15
33
|
def add_tokenable_options(key, **params)
|
16
34
|
@options[key] = params
|
17
35
|
end
|
18
36
|
|
37
|
+
# Retrieves the `required_params` for a tokenable_action, either as set by the application, or by the default<br /> Used to update model attributes as needed for a given tokenable action
|
38
|
+
# @param [Symbol] key the tokenable action
|
39
|
+
# @return [Array] the `required_params` for a tokenable action
|
19
40
|
def get_required_params(key)
|
20
41
|
get_option(key, :required_params)
|
21
42
|
end
|
22
43
|
|
44
|
+
# Retrieves the `token_lifetime` for a tokenable action, either as set by the application, or by the default
|
45
|
+
# @param [Symbol] key the tokenable action
|
46
|
+
# @return [Integer] the `token_lifetime` for a tokenable action
|
23
47
|
def get_token_lifetime(key)
|
24
48
|
get_option(key, :token_lifetime)
|
25
49
|
end
|
26
50
|
|
51
|
+
# Retrieves the `token_length` for a tokenable action, either as set by the application, or by the default
|
52
|
+
# @param [Symbol] key the tokenable action
|
53
|
+
# @return [Integer] the `token_length` for a tokenable action
|
27
54
|
def get_token_length(key)
|
28
55
|
get_option(key, :token_length)
|
29
56
|
end
|
30
57
|
|
58
|
+
# Determines whether options are provided for a tokenable action
|
59
|
+
# @param [Symbol] key the tokenable action
|
60
|
+
# @return [Boolean] `true` => options are set; `false` => options are not set
|
31
61
|
def options_set?(key)
|
32
62
|
@options.key? key
|
33
63
|
end
|
data/lib/token_master/core.rb
CHANGED
@@ -2,10 +2,21 @@ require 'token_master/error'
|
|
2
2
|
require 'securerandom'
|
3
3
|
|
4
4
|
module TokenMaster
|
5
|
-
#
|
5
|
+
# `TokenMaster::Core` provides the core functionality of the TokenMaster gem. The `Core` module performs all of the logic of completing tokenable actions, and provides descriptive messages of the status or abilities of calls made.
|
6
6
|
module Core
|
7
7
|
class << self
|
8
|
-
|
8
|
+
|
9
|
+
# Completes the tokenable action for a tokenable model instance using a token, setting `tokenable_completed_at` to the time at completion
|
10
|
+
# @param [Object] klass the tokenable Class
|
11
|
+
# @param [String, Symbol] key the tokenable action
|
12
|
+
# @param token [String] the tokenable's token used to complete the action
|
13
|
+
# @param params [Symbol=>String] keyword arguments required to complete the tokenable action
|
14
|
+
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
|
15
|
+
# @raise [TokenNotFoundError] if a tokenable instance cannot be found by the given token
|
16
|
+
# @raise [TokenCompletedError] if the tokenable action has already been completed, i.e., the tokenable instance has a timestamp in `tokenable_completed_at`
|
17
|
+
# @raise [TokenExpiredError] if the token is expired, i.e., the date is beyond the token's `created_at` plus `token_lifetime`
|
18
|
+
# @raise [MissingRequiredParamsError] if the params required by a tokenable are not provided
|
19
|
+
# @return [Object] tokenable Class instance
|
9
20
|
def do_by_token!(klass, key, token, **params)
|
10
21
|
check_manageable! klass, key
|
11
22
|
token_column = { token_col(key) => token }
|
@@ -19,7 +30,16 @@ module TokenMaster
|
|
19
30
|
model
|
20
31
|
end
|
21
32
|
|
22
|
-
#
|
33
|
+
# Completes the token action for a tokenable instance _without_ a token, setting the `tokenable_completed_at` to the time at completion.<br /> Usually implemented when you want to complete multiple tokenable actions at once, e.g., a user completes the invite action by setting up passwords, by default also completes the confirm action
|
34
|
+
# @example Force a Tokenable Action (Confirm)
|
35
|
+
# user.force_confirm! =>
|
36
|
+
# <User id: 205, name: "John Smith", email: "jsmith@example.com", confirm_token: nil, confirm_created_at: nil, confirm_sent_at: nil, confirm_completed_at: "2017-04-25 14:17:13">
|
37
|
+
# @param [Object] model the tokenable model instance
|
38
|
+
# @param [String, Symbol] key the tokenable action
|
39
|
+
# @param [Symbol=>String] params keyword arguments required to complete the tokenable action
|
40
|
+
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
|
41
|
+
# @raise [MissingRequiredParamsError] if the params required by a tokenable are not provided
|
42
|
+
# @return [Object] tokenable Class instance
|
23
43
|
def force_tokenable!(model, key, **params)
|
24
44
|
check_manageable! model.class, key
|
25
45
|
check_params! key, params
|
@@ -30,10 +50,14 @@ module TokenMaster
|
|
30
50
|
model
|
31
51
|
end
|
32
52
|
|
33
|
-
#
|
53
|
+
# Generates a tokenable action token, sets the token and the time of creation on the tokenable model instance
|
54
|
+
# @param [Object] model the tokenable model instance
|
55
|
+
# @param [String, Symbol] key the tokenable action
|
56
|
+
# @param [Integer] token_length the length of the generated token, method will use configuration token_length if not provided otherwise
|
57
|
+
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
|
58
|
+
# @return [String] token
|
34
59
|
def set_token!(model, key, token_length = nil)
|
35
60
|
check_manageable! model.class, key
|
36
|
-
check_configs_set! key
|
37
61
|
token_length ||= TokenMaster.config.get_token_length(key.to_sym)
|
38
62
|
token = generate_token token_length
|
39
63
|
|
@@ -47,7 +71,16 @@ module TokenMaster
|
|
47
71
|
token
|
48
72
|
end
|
49
73
|
|
50
|
-
#
|
74
|
+
# Accepts a block to pass on a generated token through a block, such as a mailer method, and sets `tokenable_sent_at` to the time the method is called
|
75
|
+
# @example Send Reset Instructions
|
76
|
+
# user.send_reset_instruction! { user.send_email } =>
|
77
|
+
# <User id: 205, name: "John Smith", email: "jsmith@example.com", reset_token: "3YcHkTJ7kXwV5wM", reset_created_at: 2017-04-25 14:20:54", reset_sent_at: "2017-04-25 14:22:42", reset_completed_at: nil>
|
78
|
+
# @param [Object] model the tokenable model instance
|
79
|
+
# @param [String, Symbol] key the tokenable action
|
80
|
+
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
|
81
|
+
# @raise [TokenNotSetError] if the tokenable model instance does not have a token for the tokenable action
|
82
|
+
# @raise [TokenSentError] if this has already been called for the instance and tokenable action, i.e., `tokenable_sent_at` is not `nil`
|
83
|
+
# @return [Object] tokenable model instance
|
51
84
|
def send_instructions!(model, key)
|
52
85
|
check_manageable! model.class, key
|
53
86
|
check_token_set! model, key
|
@@ -59,7 +92,16 @@ module TokenMaster
|
|
59
92
|
model.save(validate: false)
|
60
93
|
end
|
61
94
|
|
62
|
-
#
|
95
|
+
# Provides the status of the tokenable action, whether the action has been completed, the token has been sent, the token is expired, or the token has only been created
|
96
|
+
# @param [Object] model the tokenable model instance
|
97
|
+
# @param [String, Symbol] key the tokenable action
|
98
|
+
# @raise [NotTokenableError] if the provided Class does not have the correct tokenable column
|
99
|
+
# @return [String] status of the tokenable action:
|
100
|
+
# * completed
|
101
|
+
# * sent
|
102
|
+
# * expired
|
103
|
+
# * created
|
104
|
+
# * no token
|
63
105
|
def status(model, key)
|
64
106
|
check_manageable! model.class, key
|
65
107
|
return 'completed' if completed?(model, key)
|
@@ -93,12 +135,8 @@ module TokenMaster
|
|
93
135
|
TokenMaster.config.get_token_lifetime(key.to_sym)
|
94
136
|
end
|
95
137
|
|
96
|
-
def required_params(key)
|
97
|
-
TokenMaster.config.required_params(key.to_sym)
|
98
|
-
end
|
99
|
-
|
100
138
|
def check_manageable!(klass, key)
|
101
|
-
raise NotTokenable, "#{klass} not #{key}able" unless manageable?(klass, key)
|
139
|
+
raise Errors::NotTokenable, "#{klass} not #{key}able" unless manageable?(klass, key)
|
102
140
|
end
|
103
141
|
|
104
142
|
def manageable?(klass, key)
|
@@ -112,21 +150,17 @@ module TokenMaster
|
|
112
150
|
).all? { |attr| column_names.include? attr }
|
113
151
|
end
|
114
152
|
|
115
|
-
def check_configs_set!(key)
|
116
|
-
raise NotConfigured, 'You have not set the configurations for this tokenable.' unless TokenMaster.config.options_set?(key.to_sym)
|
117
|
-
end
|
118
|
-
|
119
153
|
def check_params!(key, params)
|
120
154
|
required_params = TokenMaster.config.get_required_params(key.to_sym)
|
121
|
-
raise MissingRequiredParams, 'You did not pass in the required params for this tokenable' unless required_params.all? do |k|
|
155
|
+
raise Errors::MissingRequiredParams, 'You did not pass in the required params for this tokenable' unless required_params.all? do |k|
|
122
156
|
params.keys.include? k
|
123
157
|
end
|
124
158
|
end
|
125
159
|
|
126
160
|
def check_token_active!(model, key)
|
127
|
-
raise TokenNotFound, "#{key} token not found" unless model
|
128
|
-
raise TokenCompleted, "#{key} already completed" if completed?(model, key)
|
129
|
-
raise TokenExpired, "#{key} token expired" unless token_active?(model, key)
|
161
|
+
raise Errors::TokenNotFound, "#{key} token not found" unless model
|
162
|
+
raise Errors::TokenCompleted, "#{key} already completed" if completed?(model, key)
|
163
|
+
raise Errors::TokenExpired, "#{key} token expired" unless token_active?(model, key)
|
130
164
|
end
|
131
165
|
|
132
166
|
def token_active?(model, key)
|
@@ -136,7 +170,7 @@ module TokenMaster
|
|
136
170
|
end
|
137
171
|
|
138
172
|
def check_instructions_sent!(model, key)
|
139
|
-
raise TokenSent, "#{key} already sent" if instructions_sent?(model, key)
|
173
|
+
raise Errors::TokenSent, "#{key} already sent" if instructions_sent?(model, key)
|
140
174
|
end
|
141
175
|
|
142
176
|
def instructions_sent?(model, key)
|
@@ -148,17 +182,13 @@ module TokenMaster
|
|
148
182
|
end
|
149
183
|
|
150
184
|
def check_token_set!(model, key)
|
151
|
-
raise TokenNotSet, "#{key}_token not set" unless token_set?(model, key)
|
185
|
+
raise Errors::TokenNotSet, "#{key}_token not set" unless token_set?(model, key)
|
152
186
|
end
|
153
187
|
|
154
188
|
def completed?(model, key)
|
155
189
|
model.send(completed_at_col(key)).present?
|
156
190
|
end
|
157
191
|
|
158
|
-
def check_completed!(model, key)
|
159
|
-
raise TokenNotCompleted, "#{key} not completed" unless completed?(model, key)
|
160
|
-
end
|
161
|
-
|
162
192
|
def generate_token(length)
|
163
193
|
rlength = (length * 3) / 4
|
164
194
|
SecureRandom.urlsafe_base64(rlength).tr('lIO0', 'sxyz')
|
data/lib/token_master/error.rb
CHANGED
@@ -1,36 +1,32 @@
|
|
1
1
|
module TokenMaster
|
2
|
+
# `TokenMaster::Errors` holds all of the error classes used in applying TokenMaster
|
3
|
+
module Errors
|
4
|
+
# The base class for all errors raised by TokenMaster
|
5
|
+
class Error < StandardError; end
|
2
6
|
|
3
|
-
|
4
|
-
|
7
|
+
# Raised when the attributes for a tokenable do not exist.
|
8
|
+
# This could result from a migration not being run or a spelling error
|
9
|
+
class NotTokenable < Error; end
|
5
10
|
|
6
|
-
|
7
|
-
|
11
|
+
# Raised when the required parameters for a tokenable are not provided.
|
12
|
+
# This typically happens with reset and invite tokenables, that might require both `password` and `password_confirmation` fields,
|
13
|
+
# but only one is provided to the method
|
14
|
+
class MissingRequiredParams < Error; end
|
8
15
|
|
9
|
-
|
10
|
-
|
11
|
-
class NotTokenable < Error; end
|
16
|
+
# Raised when the tokenable instance is not found
|
17
|
+
class TokenNotFound < Error; end
|
12
18
|
|
13
|
-
|
14
|
-
|
19
|
+
# Raised when the status of the token is reviewed, but the tokenable action has already been completed
|
20
|
+
class TokenCompleted < Error; end
|
15
21
|
|
16
|
-
|
17
|
-
|
22
|
+
# Raised when the token has expired based on the tokenable's `token_lifetime`
|
23
|
+
class TokenExpired < Error; end
|
18
24
|
|
19
|
-
|
20
|
-
|
25
|
+
# Raised when the tokenable instructions have already been sent when calling `send_tokenable_instructions!`
|
26
|
+
class TokenSent < Error; end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# TODO
|
26
|
-
class TokenNotCompleted < Error; end
|
27
|
-
|
28
|
-
# TODO
|
29
|
-
class TokenExpired < Error; end
|
30
|
-
|
31
|
-
# TODO
|
32
|
-
class TokenSent < Error; end
|
33
|
-
|
34
|
-
# TODO
|
35
|
-
class TokenNotSet < Error; end
|
28
|
+
# Raised when the tokenable model instance does not have a token set for a tokenable action
|
29
|
+
class TokenNotSet < Error; end
|
30
|
+
end
|
36
31
|
end
|
32
|
+
|
data/lib/token_master/model.rb
CHANGED
@@ -1,34 +1,41 @@
|
|
1
1
|
module TokenMaster
|
2
2
|
|
3
|
-
#
|
3
|
+
# `TokenMaster::Model` provides the interface to the app it is used in, providing access to its public methods by invoking `TokenMaster::ClassMethods` and definiing the appropriate methods on the app model(s).
|
4
4
|
module Model
|
5
|
+
# Includes `TokenMaster::Model` and extends `TokenMaster::ClassMethods` to the class it's used with (automatically included via Railties)
|
5
6
|
def self.included(base)
|
6
7
|
base.extend(ClassMethods)
|
7
8
|
end
|
8
9
|
|
10
|
+
# `TokenMaster::ClassMethods` defines methods on the tokenable Class to be used in applying TokenMaster
|
9
11
|
module ClassMethods
|
10
|
-
#
|
12
|
+
# Iterates over each of the tokenables provided in the generator arguments to define the appropriate TokenMaster methods on the tokenable model
|
11
13
|
def token_master(*tokenables)
|
12
14
|
tokenables.each do |tokenable|
|
13
15
|
# instance methods
|
14
|
-
|
16
|
+
|
17
|
+
# Defines a method on the tokenable model instance to generate a tokenable action token, e.g., `user.set_confim_token!`
|
15
18
|
define_method("set_#{tokenable}_token!") do
|
16
19
|
TokenMaster::Core.set_token!(self, tokenable)
|
17
20
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
|
22
|
+
# Defines a method on the tokenable model instance to send tokenable action instructions, e.g., `user.send_confim_instructions!`. Accepts a block with app logic to send instructions.
|
23
|
+
define_method("send_#{tokenable}_instructions!") do |&email|
|
24
|
+
TokenMaster::Core.send_instructions!(self, tokenable, &email)
|
21
25
|
end
|
22
|
-
|
26
|
+
|
27
|
+
# Defines a method on the tokenable model instance to retrieve the status of a tokenable action, e.g., `user.confim_status`
|
23
28
|
define_method("#{tokenable}_status") do
|
24
29
|
TokenMaster::Core.status(self, tokenable)
|
25
30
|
end
|
26
|
-
|
31
|
+
|
32
|
+
# Defines a method on the tokenable model instance to force the completion of a tokenable action, e.g., `user.force_confim!`. Accepts keyword arguments for `required_params`.
|
27
33
|
define_method("force_#{tokenable}!") do |**params|
|
28
34
|
TokenMaster::Core.force_tokenable!(self, tokenable, **params)
|
29
35
|
end
|
30
36
|
# class methods
|
31
|
-
|
37
|
+
|
38
|
+
# Defines a method on the tokenable model class to completed a tokenable action given a token, e.g., `User.confim_by_token!`. Takes the token and accepts any keyword arguments for `required_params`.
|
32
39
|
define_singleton_method("#{tokenable}_by_token!") do |token, **params|
|
33
40
|
TokenMaster::Core.do_by_token!(self, tokenable, token, **params)
|
34
41
|
end
|
data/lib/token_master/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: token_master
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Corwin
|
@@ -61,7 +61,6 @@ files:
|
|
61
61
|
- ".ruby-version"
|
62
62
|
- ".travis.yml"
|
63
63
|
- Gemfile
|
64
|
-
- Gemfile.lock
|
65
64
|
- LICENSE.txt
|
66
65
|
- README.md
|
67
66
|
- Rakefile
|
@@ -98,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
97
|
version: '0'
|
99
98
|
requirements: []
|
100
99
|
rubyforge_project:
|
101
|
-
rubygems_version: 2.
|
100
|
+
rubygems_version: 2.5.1
|
102
101
|
signing_key:
|
103
102
|
specification_version: 4
|
104
103
|
summary: Token Master!
|
data/Gemfile.lock
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
token_master (0.0.1)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
minitest (5.10.1)
|
10
|
-
rake (10.4.2)
|
11
|
-
yard (0.9.8)
|
12
|
-
|
13
|
-
PLATFORMS
|
14
|
-
ruby
|
15
|
-
|
16
|
-
DEPENDENCIES
|
17
|
-
minitest (~> 5.10.1)
|
18
|
-
rake (~> 10.4.2)
|
19
|
-
token_master!
|
20
|
-
yard
|
21
|
-
|
22
|
-
BUNDLED WITH
|
23
|
-
1.14.5
|