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.
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncodedToken
4
+ ##
5
+ # EncodedToken::Encryptor
6
+ #
7
+ # This class is responsible for encryption and decryption.
8
+ #
9
+ class Encryptor
10
+
11
+ # ======================================================================
12
+ # = Macros
13
+ # ======================================================================
14
+
15
+ include EncodedToken::ErrorMessages
16
+
17
+ # ======================================================================
18
+ # = Instance Methods
19
+ # ======================================================================
20
+
21
+ ##
22
+ # Validates if the given key is part of the cipher's key list.
23
+ #
24
+ # @param [String] key
25
+ # the key to validate.
26
+ #
27
+ # @return [Boolean]
28
+ # true if the key is valid, false otherwise.
29
+ #
30
+ def valid_key?(key)
31
+ @cipher.valid_key?(key)
32
+ end
33
+
34
+ ##
35
+ # Encrypts the given original string.
36
+ #
37
+ # @param [String] original_string
38
+ # the string to encrypt.
39
+ #
40
+ # @return [String]
41
+ # the encrypted string.
42
+ #
43
+ def encrypt(original_string)
44
+ process_chars(original_string, :encrypt)
45
+ end
46
+
47
+ ##
48
+ # Decrypts the given encrypted string.
49
+ #
50
+ # @param [String] encrypted_string
51
+ # the string to decrypt.
52
+ #
53
+ # @return [String]
54
+ # the decrypted string.
55
+ #
56
+ def decrypt(encrypted_string)
57
+ process_chars(encrypted_string, :decrypt)
58
+ end
59
+
60
+ ##
61
+ # Returns the random padding size for the current root key.
62
+ #
63
+ # @return [Integer]
64
+ # the padding size.
65
+ #
66
+ def padding
67
+ @cipher.padding
68
+ end
69
+
70
+ ##
71
+ # Returns the current root key.
72
+ #
73
+ # @return [String]
74
+ # the root key.
75
+ #
76
+ def key
77
+ @root_key
78
+ end
79
+
80
+ ##
81
+ # Returns the cipher instance.
82
+ #
83
+ # @return [Cipher]
84
+ # the cipher instance.
85
+ #
86
+ def cipher
87
+ @cipher
88
+ end
89
+
90
+ # ======================================================================
91
+ # = Private Instance Methods
92
+ # ======================================================================
93
+ #
94
+ private
95
+
96
+ def initialize(cipher, root_key = nil)
97
+ @cipher = cipher
98
+
99
+ if root_key
100
+ fail_with(:invalid_root_key) unless @cipher.valid_key?(root_key)
101
+ @cipher.root_key = root_key
102
+ end
103
+
104
+ @root_key = @cipher.root_key
105
+ end
106
+
107
+ def process_chars(input_string, action)
108
+ cipher.reset!
109
+ result = String.new
110
+
111
+ input_string.each_char do |char|
112
+ cipher.rotate_keys!
113
+ result << (action == :decrypt ? decrypt_char(char) : encrypt_char(char))
114
+ end
115
+ result
116
+ end
117
+
118
+
119
+ def encrypt_char(char)
120
+ position = cipher.hex_map[char]
121
+ fail_with(:invalid_encryption_char) unless position
122
+ cipher.text[position]
123
+ end
124
+
125
+ def decrypt_char(char)
126
+ position = cipher.text_map[char]
127
+ unless position
128
+ fail_with(:invalid_decryption_char)
129
+ end
130
+ cipher.hex_text[position]
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncodedToken
4
+ ##
5
+ # EncodedToken::ErrorMessages
6
+ #
7
+ # This module contains all error handling and reporting functions.
8
+ #
9
+ module ErrorMessages
10
+
11
+ # ======================================================================
12
+ # = Constants
13
+ # ======================================================================
14
+
15
+ CONFIG_ERRORS = {
16
+ invalid_cipher_count: { error_klass: ArgumentError,
17
+ message: "cypher count must be an integer between 8 and 60." },
18
+ invalid_expiry_argument: { error_klass: ArgumentError,
19
+ message: 'expiry seed must include an Integer seed, a Date or Time it expires & an Integer number of ciphers between 8 & 60 (default: 16).' },
20
+ invalid_max_input_size: { error_klass: ArgumentError,
21
+ message: 'maximum input size must be an integer greater than 0.' },
22
+ invalid_min_token_size: { error_klass: ArgumentError,
23
+ message: 'minimum token size must be an integer greater than 0.' },
24
+ invalid_seed: { error_klass: ArgumentError,
25
+ message: "seed must be an Integer, preferably with at least 5 digits. E.g. '12345'" },
26
+ no_config_change_allowed: { error_klass: RuntimeError,
27
+ message: 'configuration may not be changed once tokens have been encoded or decoded.' },
28
+ }.freeze
29
+
30
+ ENCRYPTOR_ERRORS = {
31
+ invalid_encryption_char: { error_klass: ArgumentError,
32
+ message: 'invalid encryption character, must be hexadecimal.' },
33
+ invalid_decryption_char: { error_klass: ArgumentError,
34
+ message: 'invalid decryption character.' },
35
+ invalid_root_key: { error_klass: ArgumentError,
36
+ message: 'new cipher key is not included within the cipher key_list.' },
37
+ }.freeze
38
+
39
+ ENCODER_ERRORS = {
40
+ invalid_token_characters: { error_klass: ArgumentError,
41
+ message: 'invalid token characters.' },
42
+ invalid_utf8_input: { error_klass: ArgumentError,
43
+ message: 'input must be an Integer, a String integer or a valid UTF-8 String.' },
44
+ invalid_token: { error_klass: ArgumentError,
45
+ message: 'invalid token submitted.' },
46
+ non_string_token: { error_klass: ArgumentError,
47
+ message: 'token must be a String.' },
48
+ }.freeze
49
+
50
+
51
+ CIPHER_ERRORS = {
52
+ invalid_cipher_seed: { error_klass: ArgumentError,
53
+ message: 'cipher seed must be a positive Integer.' },
54
+ unknown_cipher_seed: { error_klass: ArgumentError,
55
+ message: 'cipher seed not registered.' },
56
+ }.freeze
57
+
58
+
59
+ V1_ENCODER_ERRORS = {
60
+ invalid_id: { error_klass: ArgumentError,
61
+ message: 'ID must be an Integer, a String integer or a String UUID.' },
62
+ }.freeze
63
+
64
+ ENCODED_TOKEN_ERRORS = {
65
+ invalid_encoder: { error_klass: ArgumentError,
66
+ message: 'encoder should be blank, nil or :v1.' },
67
+ seed_not_set: { error_klass: RuntimeError,
68
+ message: "... looks like you haven't set a seed yet." }
69
+ }.freeze
70
+
71
+ ERROR_MESSAGES = {}.merge(CONFIG_ERRORS)
72
+ .merge(ENCRYPTOR_ERRORS)
73
+ .merge(ENCODER_ERRORS)
74
+ .merge(CIPHER_ERRORS)
75
+ .merge(V1_ENCODER_ERRORS)
76
+ .merge(ENCODED_TOKEN_ERRORS).freeze
77
+
78
+ # ======================================================================
79
+ # = Private Instance Methods
80
+ # ======================================================================
81
+ #
82
+ private
83
+
84
+ ##
85
+ # Raises an exception corresponding to the given error key.
86
+ #
87
+ # @param [Symbol] error_key
88
+ # the key for the error message and class.
89
+ #
90
+ # @raise [ArgumentError, RuntimeError]
91
+ #
92
+ def fail_with(error_key)
93
+ error_klass = ERROR_MESSAGES[error_key][:error_klass]
94
+ message = ERROR_MESSAGES[error_key][:message]
95
+ error = error_klass.new("\n\nERROR :=> EncodedToken: #{message}\n\n")
96
+ .tap { |e| e.set_backtrace(['EncodedToken']) unless ENV['ENCODEDTOKEN_DEBUG'] }
97
+ fail error
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EncodedToken
4
+ ##
5
+ # EncodedToken::Utils
6
+ #
7
+ # This module contains look-up and validation functions.
8
+ #
9
+ module Utils
10
+
11
+ # ======================================================================
12
+ # = Private Instance Methods
13
+ # ======================================================================
14
+ #
15
+ private
16
+
17
+ ##
18
+ # Generates a random alphanumeric string of the given size.
19
+ #
20
+ # @param [Integer] size
21
+ #
22
+ # @return [String]
23
+ #
24
+ def random_characters(size)
25
+ SecureRandom.alphanumeric(size)
26
+ end
27
+
28
+ ##
29
+ # Generates a random hex string of the given size.
30
+ #
31
+ # @param [Integer] size
32
+ #
33
+ # @return [String]
34
+ #
35
+ def random_hex(size)
36
+ EncodedToken::Cipher::HEX_CHARS.sample(size).join
37
+ end
38
+
39
+ ##
40
+ # Validates if the given object is a valid date/time.
41
+ #
42
+ # @param [Object] date
43
+ #
44
+ # @return [Boolean]
45
+ #
46
+ def valid_date?(date)
47
+ return true if date.is_a?(Date)
48
+ return true if date.is_a?(Time)
49
+ return true if date.is_a?(DateTime)
50
+
51
+ false
52
+ end
53
+
54
+ ##
55
+ # Validates if the given input can be converted to an Integer.
56
+ #
57
+ # @param [Object] input
58
+ #
59
+ # @return [Boolean]
60
+ #
61
+ def valid_integer?(input)
62
+ !!Integer(input)
63
+ rescue
64
+ false
65
+ end
66
+
67
+ ##
68
+ # Validates if the given ID matches the UUID format.
69
+ #
70
+ # @param [String] id
71
+ #
72
+ # @return [Boolean]
73
+ #
74
+ def valid_uuid_format?(id)
75
+ uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
76
+ uuid_regex.match?(id.downcase)
77
+ rescue
78
+ false
79
+ end
80
+
81
+ ##
82
+ # Validates if the given value contains only valid hex characters.
83
+ #
84
+ # @param [String] val
85
+ #
86
+ # @return [Boolean]
87
+ #
88
+ def valid_hex_text?(val)
89
+ (val.chars - __hex_text.chars).empty?
90
+ end
91
+
92
+ ##
93
+ # Validates if the given value contains only valid cipher characters.
94
+ #
95
+ # @param [String] val
96
+ #
97
+ # @return [Boolean]
98
+ #
99
+ def valid_token_text?(val)
100
+ (val.chars - __cipher_text.chars).empty?
101
+ end
102
+
103
+ def __crc8_checksum(hex_string)
104
+ data = [hex_string].pack('H*').bytes
105
+ crc = 0
106
+ data.each do |byte|
107
+ crc ^= byte
108
+ 8.times { crc = (crc & 0x80 != 0) ? ((crc << 1) ^ 0x07) : (crc << 1) }
109
+ crc &= 0xFF
110
+ end
111
+ crc
112
+ end
113
+
114
+ def __hex_text
115
+ EncodedToken::Cipher::HEX_TEXT
116
+ end
117
+
118
+ def __cipher_text
119
+ EncodedToken::Cipher::CIPHER_TEXT
120
+ end
121
+
122
+ def __min_token_size
123
+ EncodedToken::Encoder.min_token_size
124
+ end
125
+
126
+ def __max_input_size
127
+ EncodedToken::Encoder.max_input_size
128
+ end
129
+
130
+ end
131
+ end
@@ -1,28 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
-
4
- ##
5
- # EncodedToken version details
6
- #
7
- class EncodedToken # :nodoc:
3
+ module EncodedToken
8
4
 
9
5
  ##
10
- # The EncodedToken gem version.
6
+ # Returns the gem version.
11
7
  #
12
- # [returns:]
13
- # - the version of the currently loaded EncodedToken as a <tt>Gem::Version</tt>
8
+ # @return [Gem::Version]
14
9
  #
15
10
  def self.gem_version
16
11
  Gem::Version.new VERSION::STRING
17
12
  end
18
13
 
14
+ ##
15
+ # EncodedToken::VERSION
16
+ #
17
+ # This module represent the current version.
18
+ #
19
+ module VERSION
19
20
 
20
-
21
- module VERSION # :nodoc: all
22
-
23
- MAJOR = 1
21
+ MAJOR = 2
24
22
  MINOR = 0
25
- TINY = 2
23
+ TINY = 0
26
24
  # MICRO = ''
27
25
 
28
26
  STRING = [MAJOR, MINOR, TINY].compact.join(".")
data/lib/encoded_token.rb CHANGED
@@ -1,15 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "date"
4
+ require "securerandom"
5
+ require_relative "encoded_token/error_messages.rb"
6
+ require_relative "encoded_token/cipher.rb"
7
+ require_relative "encoded_token/utils.rb"
8
+ require_relative "encoded_token/configuration.rb"
9
+ require_relative "encoded_token/encryptor.rb"
10
+ require_relative "encoded_token/encoder"
11
+ require_relative "encoded_token/encoder/legacy_encoder.rb"
12
+ require_relative "encoded_token/encoder/legacy_decoder.rb"
13
+ require_relative "encoded_token/encoder/utf8_encoder.rb"
14
+ require_relative "encoded_token/encoder/utf8_decoder.rb"
15
+ require_relative "encoded_token/version.rb"
16
+
3
17
  ##
4
18
  # = EncodedToken
5
19
  #
6
- # Encodes a UUID or numeric ID to produce a Secure Token,
7
- # then decodes the Secure Token to return the origianl ID.
20
+ # Encodes a UTF-8 String to produce a Secure Token,
21
+ # then decodes the Secure Token to return the original input.
8
22
  #
9
- # - The given ID is encoded using a substitution cipher, then padded
23
+ # - The given input is encoded using a substitution cipher, then padded
10
24
  # with alphanumeric characters to a random length.
11
25
  #
12
- # - Multiple substituion ciphers are used to improve security.
26
+ # - Multiple substitution ciphers are used to improve security.
13
27
  #
14
28
  # *examples:*
15
29
  #
@@ -19,30 +33,114 @@
19
33
  # EncodedToken.decode("b4ex6AEB62jlBGpVAGNou8iRmD7pnHGHafQlAHB7w0J")
20
34
  # # => "12345"
21
35
  #
22
- class EncodedToken
23
-
36
+ module EncodedToken
37
+
38
+ # ======================================================================
39
+ # = Macros
40
+ # ======================================================================
41
+
42
+ extend ErrorMessages
24
43
 
25
44
  # ======================================================================
26
- # Macros
45
+ # = Class Methods
27
46
  # ======================================================================
47
+
48
+ class << self
28
49
 
29
- require "securerandom"
30
- require_relative "encoded_token/base.rb"
31
- require_relative "encoded_token/encoder.rb"
32
- require_relative "encoded_token/decoder.rb"
50
+ ##
51
+ # Returns the configuration instance.
52
+ #
53
+ # @return [Configuration]
54
+ # a memoized instance of the Configuration class.
55
+ #
56
+ def configuration
57
+ @configuration ||= Configuration.instance
58
+ end
33
59
 
34
- extend EncodedToken::Base
35
- extend EncodedToken::Encoder
36
- extend EncodedToken::Decoder
60
+ ##
61
+ # Applies the provided block to the configuration and builds the ciphers.
62
+ #
63
+ # @yield [block]
64
+ # a configuration block
65
+ #
66
+ # @return [void]
67
+ #
68
+ # @example
69
+ # EncodedToken.configure do |config|
70
+ # config.seed = 12345
71
+ # end
72
+ #
73
+ def configure
74
+ yield(configuration)
37
75
 
76
+ Encoder.build_ciphers!
77
+ end
38
78
 
39
- # ======================================================================
40
- # Public Instance Methods
41
- # ======================================================================
79
+ ##
80
+ # Encodes a given input to an encoded token.
81
+ #
82
+ # @param [Integer, String, *.to_s] input
83
+ # any object that responds to '.to_s'
84
+ # @param [:utf8, :legacy] encoder
85
+ # the encoder to use.
86
+ #
87
+ # @return [String] if successful
88
+ # @return [nil] if unsuccessful
89
+ #
90
+ def encode(input, encoder = :utf8)
91
+ Encoder.encode(input, encoder)
92
+ end
93
+
94
+ ##
95
+ # Encodes a given input to an encoded token, raising an exception
96
+ # if encountered
97
+ #
98
+ # @param [Integer, String, *.to_s] input
99
+ # any object that responds to '.to_s'
100
+ # @param [:utf8, :legacy] encoder
101
+ # the encoding to use.
102
+ #
103
+ # @return [String] the encoded token
104
+ #
105
+ # @raise [ArgumentError, RuntimeError]
106
+ # if the encoding fails
107
+ #
108
+ def encode!(input, encoder = :utf8)
109
+ Encoder.encode!(input, encoder)
110
+ end
42
111
 
43
- # This is an abstract class, so ensure no instantiation
44
- def initialize # :nodoc:
45
- raise NotImplementedError.new("SecureToken is an abstract class and cannot be instantiated.")
46
- end
112
+
113
+ ##
114
+ # Decodes a given token to the original string
115
+ #
116
+ # @param [String] token
117
+ # an encoded-token string
118
+ #
119
+ # @return [String]
120
+ # the decoded value if successful
121
+ # @return [nil]
122
+ # if unsuccessful
123
+ #
124
+ def decode(token)
125
+ Encoder.decode(token)
126
+ end
47
127
 
48
- end #class
128
+
129
+ ##
130
+ # Decodes a given token to the original string, raising an exception
131
+ # if encountered
132
+ #
133
+ # @param [String] token
134
+ # an encoded-token string
135
+ #
136
+ # @return [String]
137
+ # the decoded value
138
+ #
139
+ # @raise [ArgumentError, RuntimeError]
140
+ # if the decoding fails
141
+ #
142
+ def decode!(token)
143
+ Encoder.decode!(token)
144
+ end
145
+ end # class << self
146
+ end #module
metadata CHANGED
@@ -1,27 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: encoded_token
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - CodeMeister
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-10-05 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: Stop hitting the DB with every secure-token submission. Encoded Tokens
14
- have the ID, or UUID, encoded within the token itself - increasing both security
15
- and performance. Coded in plain Ruby, EncodedToken is framework agnostic.
12
+ description: Replacing secure-tokens, encoded-tokens are verified BEFORE accessing
13
+ the database, increasing both security and performance. Coded in plain Ruby, EncodedToken
14
+ is framework agnostic.
16
15
  email: encoded_token@codemeister.dev
17
16
  executables: []
18
17
  extensions: []
19
18
  extra_rdoc_files: []
20
19
  files:
21
20
  - lib/encoded_token.rb
22
- - lib/encoded_token/base.rb
23
- - lib/encoded_token/decoder.rb
21
+ - lib/encoded_token/cipher.rb
22
+ - lib/encoded_token/configuration.rb
24
23
  - lib/encoded_token/encoder.rb
24
+ - lib/encoded_token/encoder/legacy_decoder.rb
25
+ - lib/encoded_token/encoder/legacy_encoder.rb
26
+ - lib/encoded_token/encoder/utf8_decoder.rb
27
+ - lib/encoded_token/encoder/utf8_encoder.rb
28
+ - lib/encoded_token/encryptor.rb
29
+ - lib/encoded_token/error_messages.rb
30
+ - lib/encoded_token/utils.rb
25
31
  - lib/encoded_token/version.rb
26
32
  homepage: https://github.com/Rubology/encoded_token
27
33
  licenses:
@@ -30,7 +36,6 @@ metadata:
30
36
  homepage_uri: https://github.com/Rubology/encoded_token
31
37
  source_code_uri: https://github.com/Rubology/encoded_token
32
38
  changelog_uri: https://github.com/Rubology/encoded_token/blob/master/CHANGELOG.md
33
- post_install_message:
34
39
  rdoc_options: []
35
40
  require_paths:
36
41
  - lib
@@ -38,16 +43,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
43
  requirements:
39
44
  - - ">="
40
45
  - !ruby/object:Gem::Version
41
- version: 2.5.0
46
+ version: 2.6.0
42
47
  required_rubygems_version: !ruby/object:Gem::Requirement
43
48
  requirements:
44
49
  - - ">="
45
50
  - !ruby/object:Gem::Version
46
51
  version: '0'
47
52
  requirements: []
48
- rubygems_version: 3.3.22
49
- signing_key:
53
+ rubygems_version: 4.0.17
50
54
  specification_version: 4
51
- summary: A better, more secure and efficient way to manage secure-tokens - by encoding
52
- the ID, or UUID, within the token itself.
55
+ summary: A more secure and efficient way to manage secure-tokens.
53
56
  test_files: []