encoded_token 1.0.2 → 2.0.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.
@@ -1,280 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ##
4
- # EncodedToken::Base
5
- #
6
- # The core configuration settings used for encoding and decoding, along
7
- # with the methods for initialization and verification.
8
- #
9
- # Encoding is achived though a variation of Alberti's cipher for
10
- # multiple Ciphertext Alphabets.
11
- # (https://en.wikipedia.org/wiki/Alberti_cipher
12
- #
13
- class EncodedToken
14
- module Base
15
-
16
- # ======================================================================
17
- # Configuration
18
- # ======================================================================
19
-
20
- HEX_NUMS = ('0'..'9').to_a
21
- HEX_CHARS = ('a'..'f').to_a + ('A'..'F').to_a
22
- SPECIAL_CHARS = ['-']
23
- HEX_TEXT = (HEX_NUMS + HEX_CHARS + SPECIAL_CHARS).join # :nodoc:
24
-
25
- CIPHER_CHARS = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
26
- CIPHER_TEXT = CIPHER_CHARS.join # :nodoc:
27
- CIPHER_COUNT = 16 # :nodoc:
28
- TARGET_SIZE = 55 # :nodoc:
29
-
30
- private_constant :HEX_NUMS, :HEX_CHARS, :SPECIAL_CHARS, :CIPHER_CHARS
31
-
32
- @@seed = nil
33
- @@ciphers = nil
34
- @@keylist = nil
35
-
36
-
37
-
38
- # ======================================================================
39
- # Public Methods
40
- # ======================================================================
41
-
42
- ##
43
- # Sets the seed to be used in generating a random encoding
44
- #
45
- # [returns:]
46
- # - true on success
47
- #
48
- # [on error:]
49
- # - raises an exception
50
- #
51
- def seed=(new_seed)
52
- if @@seed
53
- fail_with_seed_already_set
54
- else
55
- @@seed = parse_seed(new_seed)
56
- generate_ciphers
57
- return true
58
- end
59
- end
60
-
61
-
62
-
63
- # ======================================================================
64
- # Class Private Methods
65
- # ======================================================================
66
- private
67
-
68
-
69
- # parse the new seed to ensure it is an integer
70
- #
71
- # return the Integer seed on success, otherwise raises an error
72
- #
73
- def parse_seed(new_seed)
74
- if valid_integer?(new_seed)
75
- return new_seed.to_i.abs
76
- else
77
- fail_with_invalid_seed_argument
78
- end
79
- end
80
-
81
-
82
-
83
- # Generate a set of ciphers
84
- #
85
- # returns - a Hash of ciphers with a CIPHER_CHARS character for each key
86
- #
87
- def generate_ciphers
88
- ciphers = {}
89
- random = Random.new(__seed)
90
- keys = CIPHER_CHARS.sample(__cipher_count, random: random).sort_by(&:downcase)
91
- @@keylist = keys
92
-
93
- # for each key, add a hash of the padding chareacter count
94
- # and a cipher string to be used for encryption, using a different seed each time
95
- keys.each_with_index do |key, idx|
96
- ciphers[key] = {
97
- padding: random.rand(0..10),
98
- cipher_text: CIPHER_CHARS.sample(HEX_TEXT.size, random: random).join
99
- }
100
- end
101
-
102
- @@ciphers = ciphers
103
- end
104
-
105
-
106
-
107
- # return the next cypher key after the given key, looping to the first when required
108
- def rotate_cipher_key(key)
109
- idx = __keylist.index(key) + 1
110
- __keylist[idx] || __keylist.first
111
- end
112
-
113
-
114
-
115
- # Validity
116
- # ======================================================================
117
-
118
- # checks if the seed had been set
119
- # - returns true if the seed is set
120
- # - set the seed if missing and a valid ENV['ENCODED_TOKEN_SEED'] is present
121
- #
122
- def assert_valid_seed!
123
- case
124
- when !!@@seed
125
- true
126
-
127
- when !!ENV['ENCODED_TOKEN_SEED']
128
- assert_valid_env!
129
- self.seed = ENV['ENCODED_TOKEN_SEED'].to_i
130
-
131
- else
132
- fail RuntimeError, "Encryption seed must be set before using EncodedToken."\
133
- " Set the seed with EncodedToken.seed=(xxx)."
134
- end
135
- end
136
-
137
-
138
-
139
- # check ENV['ENCODED_TOKEN_SEED'] is a string integer
140
- def assert_valid_env!
141
- begin
142
- if valid_integer?(ENV['ENCODED_TOKEN_SEED'])
143
- return true
144
- else
145
- fail
146
- end
147
- rescue
148
- fail RuntimeError, "ENV['ENCODED_TOKEN_SEED'] must be a string encoded Integer."
149
- end
150
- end
151
-
152
-
153
-
154
- # Return true if the given String only contains hex text
155
- def valid_hex_text?(val)
156
- (val.chars - __hex_text.chars).empty?
157
- end
158
-
159
-
160
-
161
- # returns true if the given id is is an integer, else false
162
- #
163
- # id - and Inetger or String
164
- #
165
- def valid_integer?(id)
166
- sid = id.to_s
167
- sid.to_i.to_s == sid
168
- rescue
169
- false
170
- end
171
-
172
-
173
-
174
- # Return true if the given String only contains cipher text text
175
- def valid_token_text?(val)
176
- (val.chars - __cipher_text.chars).empty?
177
- end
178
-
179
-
180
-
181
- # returns true if the given id is a UUID, else false
182
- #
183
- # id - String uuid
184
- #
185
- def valid_uuid_format?(id)
186
- uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
187
- uuid_regex.match?(id.downcase)
188
- rescue
189
- false
190
- end
191
-
192
-
193
-
194
- # Configuration Attributes
195
- # ======================================================================
196
-
197
- # return the number of ciphers
198
- def __cipher_count
199
- CIPHER_COUNT
200
- end
201
-
202
-
203
-
204
- # return the cipher keylist
205
- def __keylist
206
- @@keylist
207
- end
208
-
209
-
210
-
211
- # return the constant cypher text
212
- def __cipher_text
213
- CIPHER_TEXT
214
- end
215
-
216
-
217
-
218
- # return the base ciphers hash
219
- def __ciphers
220
- @@ciphers
221
- end
222
-
223
-
224
-
225
- # return the constant hex text
226
- def __hex_text
227
- HEX_TEXT
228
- end
229
-
230
-
231
-
232
- # return seed
233
- def __seed
234
- @@seed
235
- end
236
-
237
-
238
-
239
- # return the target size
240
- def __target_size
241
- TARGET_SIZE
242
- end
243
-
244
-
245
-
246
- # Error Messages
247
- # ======================================================================
248
-
249
- # error: invalid ID supplied
250
- def fail_with_invalid_id_argument
251
- fail_with ArgumentError,
252
- ":id must be an Integer, a String integer or a String UUID."
253
- end
254
-
255
-
256
-
257
- # error: Seed is already set
258
- def fail_with_seed_already_set
259
- fail_with ArgumentError,
260
- "EncodedToken seed has alreay been set to #{@@seed}."
261
- end
262
-
263
-
264
-
265
- # error: invalid Seed supplied
266
- def fail_with_invalid_seed_argument
267
- fail_with ArgumentError,
268
- ":seed must be an Integer, preferably with at least 5 digits."
269
- end
270
-
271
-
272
-
273
- # default error message header
274
- def fail_with(error_klass, message)
275
- fail error_klass, "\n\nERROR :=> EncodedToken: #{message}\n\n"
276
- end
277
-
278
-
279
- end #module
280
- end #class
@@ -1,178 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ##
4
- # EncodedToken::Encoder
5
- #
6
- # The methods required to decode a token.
7
- #
8
- class EncodedToken # :nodoc:
9
- module Decoder
10
-
11
- # ======================================================================
12
- # Public Methods
13
- # ======================================================================
14
-
15
- ##
16
- # Decode a previously encoded token to return the original ID
17
- #
18
- # [args:]
19
- # - *token* [String]
20
- #
21
- # [returns:]
22
- # - a String with the original ID
23
- #
24
- # [on error:]
25
- # - an invalid String token returns +nil+
26
- # - otherwise an exception will be raised
27
- #
28
- # *examples:*
29
- #
30
- # EncodedToken.decode!("KY3bnaRGmyy6yJS3imWr1dcWtzDYvZjpIAYyCUo5PEKPFvQgtTTed")
31
- # #=> "12345"
32
- #
33
- # EncodedToken.decode!("3gDwO7r4UJYeBYDBLU94MqjZQm0SToSE29ACDNcw0xf4QusZKxQHJ")
34
- # #=> "12345"
35
- #
36
- # EncodedToken.decode!("pAi1SmpKgFAchh76EoLbYLeXVQmLwmMlH2v1zDVeufioKGr0709Qw")
37
- # #=> "468a5eeb-0cda-4c99-8dba-6a96c33003e0"
38
- #
39
- # EncodedToken.decode!("abcdefghijklmnopqrstuvwxyz")
40
- # #=> nil
41
- #
42
- # EncodedToken.decode!(:test)
43
- # #=> Token is not a string. (RuntimeError)
44
- #
45
- def decode!(token)
46
- assert_valid_seed!
47
-
48
- token = sanitize_token(token)
49
- id = parse_token(token)
50
-
51
- # is it a UUID or numeric ID
52
- if valid_integer?(id) || valid_uuid_format?(id)
53
- return id
54
- else
55
- return nil
56
- end
57
- end
58
-
59
-
60
-
61
- ##
62
- # Decode a previously encoded token to return the original ID
63
- #
64
- # [args:]
65
- # - *token* [String]
66
- #
67
- # [returns:]
68
- # - a String with the original ID
69
- #
70
- # [on error:]
71
- # - returns +nil+
72
- #
73
- # *examples:*
74
- #
75
- # EncodedToken.decode("KY3bnaRGmyy6yJS3imWr1dcWtzDYvZjpIAYyCUo5PEKPFvQgtTTed")
76
- # #=> "12345"
77
- #
78
- # EncodedToken.decode("3gDwO7r4UJYeBYDBLU94MqjZQm0SToSE29ACDNcw0xf4QusZKxQHJ")
79
- # #=> "12345"
80
- #
81
- # EncodedToken.decode("pAi1SmpKgFAchh76EoLbYLeXVQmLwmMlH2v1zDVeufioKGr0709Qw")
82
- # #=> "4ef2091f-023b-4af6-9e9f-f46465f897ba"
83
- #
84
- # EncodedToken.decode("abcdefghijklmnopqrstuvwxyz")
85
- # #=> nil
86
- #
87
- # EncodedToken.decode(:test)
88
- # #=> nil
89
- #
90
- def decode(id)
91
- decode!(id)
92
- rescue
93
- nil
94
- end
95
-
96
-
97
- # ======================================================================
98
- # Private Methods
99
- # ======================================================================
100
- #
101
- private
102
-
103
-
104
- # ensures the given token is valid to decode
105
- #
106
- # token [String] - a properly encoded String
107
- #
108
- # returns - a String duplicate of the given token
109
- #
110
- # on error: - a RuntimeError is raised
111
- #
112
- # NOTE: - we return a duplicate so the original is not changed later
113
- # in the process when shifting segments
114
- #
115
- def sanitize_token(token)
116
- fail 'Token is not a string.' unless token.is_a?(String)
117
- fail 'Invalid token characters' unless valid_token_text?(token)
118
- fail 'Invalid token cipher.' unless __keylist.include?(token[0])
119
- token.dup
120
- end
121
-
122
-
123
-
124
- # Parses the token to retrieve the original ID
125
- #
126
- # token [String] - the encoded token
127
- #
128
- # returns [String] - the original ID
129
- #
130
- def parse_token(token)
131
- key = token[0]
132
- id_size = decrypt_size(token[1,2], key)
133
- padding = __ciphers[key][:padding]
134
- enc_id = token[padding + 3, id_size]
135
-
136
- return decrypt(enc_id, key)
137
- end
138
-
139
-
140
-
141
- # returns the Integer size of the id
142
- #
143
- # enc_size - the encrypted ID
144
- # key - the cipher key to use
145
- #
146
- def decrypt_size(enc_size, key)
147
- decrypt(enc_size, key).hex
148
- end
149
-
150
-
151
-
152
- # decrypt the id using the cipher text from the given key.
153
- # - rotate the cipher every character
154
- #
155
- # enc_id - encoded String ID
156
- # key - base cipher key to use
157
- #
158
- # on error - rasies an exception (invalid cipher chars, etc)
159
- #
160
- def decrypt(enc_id, key)
161
- id = ""
162
- enc_key = key
163
-
164
- enc_id.each_char do |char|
165
- enc_key = rotate_cipher_key(enc_key)
166
- cipher_text = __ciphers[enc_key][:cipher_text]
167
-
168
- id += __hex_text[cipher_text.index(char)]
169
- end
170
-
171
- return id
172
- rescue
173
- fail 'Invalid token characters'
174
- end
175
-
176
- end #module
177
- end #class
178
-