glogin 0.16.4 → 0.17.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.
data/LICENSES/MIT.txt ADDED
@@ -0,0 +1,21 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2017-2025 Yegor Bugayenko
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.
data/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # OAuth Login via GitHub Made Simple
2
2
 
3
- [![DevOps By Rultor.com](http://www.rultor.com/b/yegor256/glogin)](http://www.rultor.com/p/yegor256/glogin)
3
+ [![DevOps By Rultor.com](https://www.rultor.com/b/yegor256/glogin)](https://www.rultor.com/p/yegor256/glogin)
4
4
  [![We recommend RubyMine](https://www.elegantobjects.org/rubymine.svg)](https://www.jetbrains.com/ruby/)
5
5
 
6
6
  [![rake](https://github.com/yegor256/glogin/actions/workflows/rake.yml/badge.svg)](https://github.com/yegor256/glogin/actions/workflows/rake.yml)
7
- [![PDD status](http://www.0pdd.com/svg?name=yegor256/glogin)](http://www.0pdd.com/p?name=yegor256/glogin)
8
- [![Gem Version](https://badge.fury.io/rb/glogin.svg)](http://badge.fury.io/rb/glogin)
7
+ [![PDD status](https://www.0pdd.com/svg?name=yegor256/glogin)](https://www.0pdd.com/p?name=yegor256/glogin)
8
+ [![Gem Version](https://badge.fury.io/rb/glogin.svg)](https://badge.fury.io/rb/glogin)
9
9
  [![Maintainability](https://api.codeclimate.com/v1/badges/155f86b639d155259219/maintainability)](https://codeclimate.com/github/yegor256/glogin/maintainability)
10
10
  [![Test Coverage](https://img.shields.io/codecov/c/github/yegor256/glogin.svg)](https://codecov.io/github/yegor256/glogin?branch=master)
11
- [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/yegor256/glogin/master/frames)
11
+ [![Yard Docs](https://img.shields.io/badge/yard-docs-blue.svg)](https://rubydoc.info/github/yegor256/glogin/master/frames)
12
12
  [![Hits-of-Code](https://hitsofcode.com/github/yegor256/glogin)](https://hitsofcode.com/view/github/yegor256/glogin)
13
13
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/yegor256/glogin/blob/master/LICENSE.txt)
14
14
 
@@ -27,7 +27,7 @@ First, somewhere in the global space, before the app starts:
27
27
  require 'glogin'
28
28
  configure do
29
29
  set :glogin, GLogin::Auth.new(
30
- # Make sure their values are coming from a secure
30
+ # Make sure these values are coming from a secure
31
31
  # place and are not visible in the source code:
32
32
  client_id, client_secret,
33
33
  # This is what you will register in GitHub as an
@@ -59,11 +59,11 @@ before '/*' do
59
59
  end
60
60
  ```
61
61
 
62
- If the `glogin` cookie is coming in and contains a valid data,
62
+ If the `glogin` cookie is coming in and contains valid data,
63
63
  a local variable `@user` will be set to something like this:
64
64
 
65
65
  ```ruby
66
- { login: 'yegor256', avatar: 'http://...' }
66
+ { 'id' => '526301', 'login' => 'yegor256', 'avatar' => 'http://...' }
67
67
  ```
68
68
 
69
69
  If the `secret` is an empty string, the encryption will be disabled.
@@ -103,10 +103,10 @@ it is:
103
103
  settings.glogin.login_uri
104
104
  ```
105
105
 
106
- For unit testing you can just provide an empty string as a `secret` for
106
+ For unit testing, you can just provide an empty string as a `secret` for
107
107
  `GLogin::Cookie::Open` and `GLogin::Cookie::Closed`
108
108
  and the encryption will be disabled:
109
- whatever will be coming from the cookie will be trusted. For testing
109
+ whatever comes from the cookie will be trusted. For testing
110
110
  it will be convenient to provide a user name in a query string, like:
111
111
 
112
112
  ```text
@@ -134,7 +134,7 @@ Also, you can use `GLogin::Codec` just to encrypt/decrypt a piece of text:
134
134
 
135
135
  ```ruby
136
136
  require 'glogin/codec'
137
- codec = GLogin:Codec.new('the secret')
137
+ codec = GLogin::Codec.new('the secret')
138
138
  encrypted = codec.encrypt('Hello, world!')
139
139
  decrypted = codec.decrypt(encrypted)
140
140
  ```
@@ -143,7 +143,7 @@ decrypted = codec.decrypt(encrypted)
143
143
 
144
144
  Read
145
145
  [these guidelines](https://www.yegor256.com/2014/04/15/github-guidelines.html).
146
- Make sure you build is green before you contribute
146
+ Make sure your build is green before you contribute
147
147
  your pull request. You will need to have
148
148
  [Ruby](https://www.ruby-lang.org/en/) 2.3+ and
149
149
  [Bundler](https://bundler.io/) installed. Then:
data/REUSE.toml ADDED
@@ -0,0 +1,36 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2025 Yegor Bugayenko
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ version = 1
5
+ [[annotations]]
6
+ path = [
7
+ ".DS_Store",
8
+ ".gitattributes",
9
+ ".gitignore",
10
+ ".pdd",
11
+ "**.json",
12
+ "**.md",
13
+ "**.png",
14
+ "**.svg",
15
+ "**.txt",
16
+ "**/.DS_Store",
17
+ "**/.gitignore",
18
+ "**/.pdd",
19
+ "**/*.csv",
20
+ "**/*.jpg",
21
+ "**/*.json",
22
+ "**/*.md",
23
+ "**/*.pdf",
24
+ "**/*.png",
25
+ "**/*.svg",
26
+ "**/*.txt",
27
+ "**/*.vm",
28
+ "**/CNAME",
29
+ "**/Gemfile.lock",
30
+ "Gemfile.lock",
31
+ "README.md",
32
+ "renovate.json",
33
+ ]
34
+ precedence = "override"
35
+ SPDX-FileCopyrightText = "Copyright (c) 2025 Yegor Bugayenko"
36
+ SPDX-License-Identifier = "MIT"
data/Rakefile CHANGED
@@ -1,29 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  #
4
- # Copyright (c) 2017-2024 Yegor Bugayenko
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the 'Software'), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in all
14
- # copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- # SOFTWARE.
4
+ # SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
5
+ # SPDX-License-Identifier: MIT
23
6
 
24
7
  require 'rubygems'
25
8
  require 'rake'
26
- require 'rdoc'
27
9
  require 'rake/clean'
28
10
 
29
11
  def name
@@ -34,7 +16,7 @@ def version
34
16
  Gem::Specification.load(Dir['*.gemspec'].first).version
35
17
  end
36
18
 
37
- task default: %i[clean test rubocop copyright]
19
+ task default: %i[clean test rubocop yard]
38
20
 
39
21
  require 'rake/testtask'
40
22
  desc 'Run all unit tests'
@@ -46,26 +28,14 @@ Rake::TestTask.new(:test) do |test|
46
28
  test.verbose = false
47
29
  end
48
30
 
49
- require 'rdoc/task'
50
- desc 'Build RDoc documentation'
51
- Rake::RDocTask.new do |rdoc|
52
- rdoc.rdoc_dir = 'rdoc'
53
- rdoc.title = "#{name} #{version}"
54
- rdoc.rdoc_files.include('README*')
55
- rdoc.rdoc_files.include('lib/**/*.rb')
31
+ require 'yard'
32
+ desc 'Build Yard documentation'
33
+ YARD::Rake::YardocTask.new do |t|
34
+ t.files = ['lib/**/*.rb']
56
35
  end
57
36
 
58
37
  require 'rubocop/rake_task'
59
38
  desc 'Run RuboCop on all directories'
60
39
  RuboCop::RakeTask.new(:rubocop) do |task|
61
40
  task.fail_on_error = true
62
- task.requires << 'rubocop-rspec'
63
- end
64
-
65
- task :copyright do
66
- sh "grep -q -r '2017-#{Date.today.strftime('%Y')}' \
67
- --include '*.rb' \
68
- --include '*.txt' \
69
- --include 'Rakefile' \
70
- ."
71
41
  end
data/glogin.gemspec CHANGED
@@ -1,24 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2017-2024 Yegor Bugayenko
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.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
22
5
 
23
6
  require 'English'
24
7
 
@@ -42,6 +25,7 @@ Gem::Specification.new do |s|
42
25
  s.rdoc_options = ['--charset=UTF-8']
43
26
  s.extra_rdoc_files = ['README.md', 'LICENSE.txt']
44
27
  s.add_dependency 'base58', '>= 0.2'
28
+ s.add_dependency 'base64', '>= 0.2'
45
29
  s.add_dependency 'openssl', '>= 2.0'
46
30
  s.metadata['rubygems_mfa_required'] = 'true'
47
31
  end
data/lib/glogin/auth.rb CHANGED
@@ -1,40 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Copyright (c) 2017-2024 Yegor Bugayenko
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the 'Software'), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in all
14
- # copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
23
5
 
6
+ require 'cgi'
7
+ require 'json'
24
8
  require 'net/http'
25
9
  require 'uri'
26
- require 'json'
27
- require 'cgi'
28
10
 
29
11
  # GLogin main module.
30
12
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
31
- # Copyright:: Copyright (c) 2017-2024 Yegor Bugayenko
13
+ # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
32
14
  # License:: MIT
33
15
  module GLogin
16
+ # GitHub authentication mechanism.
17
+ #
18
+ # This class handles the OAuth flow with GitHub, including generating
19
+ # authorization URLs and exchanging authorization codes for access tokens
20
+ # to retrieve user information.
34
21
  #
35
- # GitHub auth mechanism
22
+ # @example Creating an Auth instance
23
+ # auth = GLogin::Auth.new(
24
+ # 'your-github-client-id',
25
+ # 'your-github-client-secret',
26
+ # 'https://yourapp.com/callback'
27
+ # )
36
28
  #
29
+ # @example Getting the GitHub login URL
30
+ # login_url = auth.login_uri
31
+ # # => "https://github.com/login/oauth/authorize?client_id=...&redirect_uri=..."
32
+ #
33
+ # @example Retrieving user information after callback
34
+ # user_info = auth.user(params[:code])
35
+ # # => {"id"=>"123456", "login"=>"username", "avatar_url"=>"https://..."}
37
36
  class Auth
37
+ # Creates a new GitHub authentication handler.
38
+ #
39
+ # @param id [String] GitHub OAuth application client ID
40
+ # @param secret [String] GitHub OAuth application client secret
41
+ # @param redirect [String] The callback URL where GitHub will redirect after authentication
42
+ # @raise [RuntimeError] if any parameter is nil or redirect is empty
43
+ # @example
44
+ # auth = GLogin::Auth.new(
45
+ # ENV['GITHUB_CLIENT_ID'],
46
+ # ENV['GITHUB_CLIENT_SECRET'],
47
+ # 'https://myapp.com/auth/callback'
48
+ # )
38
49
  def initialize(id, secret, redirect)
39
50
  raise "GitHub client ID can't be nil" if id.nil?
40
51
  @id = id
@@ -45,14 +56,39 @@ module GLogin
45
56
  @redirect = redirect
46
57
  end
47
58
 
59
+ # Generates the GitHub OAuth authorization URL.
60
+ #
61
+ # Users should be redirected to this URL to begin the authentication process.
62
+ # GitHub will ask them to authorize your application, then redirect them back
63
+ # to your specified redirect URL with an authorization code.
64
+ #
65
+ # @return [String] The GitHub OAuth authorization URL
66
+ # @example Redirect users to GitHub for authentication
67
+ # auth = GLogin::Auth.new(id, secret, redirect_url)
68
+ # redirect auth.login_uri
48
69
  def login_uri
49
70
  "https://github.com/login/oauth/authorize?client_id=#{CGI.escape(@id)}&redirect_uri=#{CGI.escape(@redirect)}"
50
71
  end
51
72
 
52
- # Returns a hash with information about Github user,
73
+ # Returns a hash with information about GitHub user
53
74
  # who just logged in with the authentication code.
54
75
  #
55
- # API: https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user
76
+ # This method exchanges the temporary authorization code (received from GitHub
77
+ # callback) for an access token, then uses that token to fetch the user's
78
+ # profile information.
79
+ #
80
+ # @param code [String] The authorization code received from GitHub callback
81
+ # @return [Hash] User information including 'id', 'login', and 'avatar_url'
82
+ # @raise [RuntimeError] if the code is nil, empty, or if the API request fails
83
+ # @example Handling the GitHub callback
84
+ # get '/auth/callback' do
85
+ # code = params[:code]
86
+ # user = auth.user(code)
87
+ # # user => {"id"=>"123456", "login"=>"octocat", "avatar_url"=>"https://..."}
88
+ # session[:user_id] = user['id']
89
+ # end
90
+ # @note When secret is empty (test mode), returns a mock user object
91
+ # @see https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user
56
92
  def user(code)
57
93
  if @secret.empty?
58
94
  return {
data/lib/glogin/codec.rb CHANGED
@@ -1,51 +1,90 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Copyright (c) 2017-2024 Yegor Bugayenko
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the 'Software'), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in all
14
- # copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
- # SOFTWARE.
3
+ # SPDX-FileCopyrightText: Copyright (c) 2017-2025 Yegor Bugayenko
4
+ # SPDX-License-Identifier: MIT
23
5
 
24
- require 'securerandom'
25
- require 'openssl'
26
- require 'digest/sha1'
27
6
  require 'base58'
28
7
  require 'base64'
8
+ require 'digest/sha1'
9
+ require 'openssl'
10
+ require 'securerandom'
29
11
 
30
12
  # Codec.
31
13
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
- # Copyright:: Copyright (c) 2017-2024 Yegor Bugayenko
14
+ # Copyright:: Copyright (c) 2017-2025 Yegor Bugayenko
33
15
  # License:: MIT
34
16
  module GLogin
35
- # The codec
17
+ # The codec for encrypting and decrypting text.
18
+ #
19
+ # This class provides symmetric encryption using AES-256-CBC. It can encode
20
+ # text using either Base64 or Base58 encoding. A random salt is added to
21
+ # each encryption to ensure that the same plaintext produces different
22
+ # ciphertexts each time.
23
+ #
24
+ # @example Basic encryption and decryption
25
+ # codec = GLogin::Codec.new('my-secret-key')
26
+ # encrypted = codec.encrypt('sensitive data')
27
+ # decrypted = codec.decrypt(encrypted)
28
+ # # => "sensitive data"
29
+ #
30
+ # @example Using Base64 encoding
31
+ # codec = GLogin::Codec.new('secret', base64: true)
32
+ # encrypted = codec.encrypt('hello world')
33
+ # # => "U29tZUJhc2U2NEVuY29kZWRTdHJpbmc="
34
+ #
35
+ # @example Test mode without encryption
36
+ # codec = GLogin::Codec.new('') # Empty secret
37
+ # encrypted = codec.encrypt('plaintext')
38
+ # # => "plaintext" (no encryption in test mode)
36
39
  class Codec
37
- # When can't decode.
40
+ # Raised when decryption fails.
41
+ #
42
+ # This can happen when:
43
+ # - The encrypted text is corrupted
44
+ # - The wrong secret key is used
45
+ # - The text is not properly encoded (Base64/Base58)
38
46
  class DecodingError < StandardError; end
39
47
 
40
- # Ctor.
41
- # +secret+: The secret to do the encoding
42
- # +base64+: If TRUE, Base-64 will be used, otherwise Base-58
48
+ # Creates a new codec instance.
49
+ #
50
+ # @param secret [String] The secret key for encryption. If empty, no encryption is performed (test mode)
51
+ # @param base64 [Boolean] Whether to use Base64 encoding (true) or Base58 encoding (false)
52
+ # @raise [RuntimeError] if secret is nil
53
+ # @example Create codec with Base58 encoding (default)
54
+ # codec = GLogin::Codec.new('my-secret-key')
55
+ #
56
+ # @example Create codec with Base64 encoding
57
+ # codec = GLogin::Codec.new('my-secret-key', base64: true)
58
+ #
59
+ # @example Create codec in test mode (no encryption)
60
+ # codec = GLogin::Codec.new('')
43
61
  def initialize(secret = '', base64: false)
44
62
  raise 'Secret can\'t be nil' if secret.nil?
45
63
  @secret = secret
46
64
  @base64 = base64
47
65
  end
48
66
 
67
+ # Decrypts an encrypted text string.
68
+ #
69
+ # @param text [String] The encrypted text to decrypt
70
+ # @return [String] The decrypted plaintext
71
+ # @raise [RuntimeError] if text is nil
72
+ # @raise [DecodingError] if decryption fails due to:
73
+ # - Invalid Base64/Base58 encoding
74
+ # - Wrong secret key
75
+ # - Corrupted ciphertext
76
+ # - Missing or invalid salt
77
+ # @example Decrypt a Base58-encoded string
78
+ # codec = GLogin::Codec.new('secret')
79
+ # plaintext = codec.decrypt('3Hs9k2LgU...')
80
+ # # => "hello world"
81
+ #
82
+ # @example Handle decryption errors
83
+ # begin
84
+ # plaintext = codec.decrypt(corrupted_text)
85
+ # rescue GLogin::Codec::DecodingError => e
86
+ # puts "Decryption failed: #{e.message}"
87
+ # end
49
88
  def decrypt(text)
50
89
  raise 'Text can\'t be nil' if text.nil?
51
90
  if @secret.empty?
@@ -55,9 +94,9 @@ module GLogin
55
94
  cpr.decrypt
56
95
  cpr.key = digest(cpr.key_len)
57
96
  if @base64
58
- raise DecodingError, 'This is not Base64' unless %r{^[a-zA-Z0-9\\+/=]+$}.match?(text)
97
+ raise DecodingError, "This is not Base64: #{text.inspect}" unless %r{^[a-zA-Z0-9\\+/=]+$}.match?(text)
59
98
  else
60
- raise DecodingError, 'This is not Base58' unless /^[a-zA-Z0-9]+$/.match?(text)
99
+ raise DecodingError, "This is not Base58: #{text.inspect}" unless /^[a-km-zA-HJ-NP-Z1-9]+$/.match?(text)
61
100
  end
62
101
  plain = @base64 ? Base64.decode64(text) : Base58.base58_to_binary(text)
63
102
  raise DecodingError if plain.empty?
@@ -73,6 +112,30 @@ module GLogin
73
112
  raise DecodingError, e.message
74
113
  end
75
114
 
115
+ # Encrypts a plaintext string.
116
+ #
117
+ # The method adds a random salt to the text before encryption to ensure
118
+ # that encrypting the same text multiple times produces different results.
119
+ # The encrypted output is encoded using either Base64 or Base58.
120
+ #
121
+ # @param text [String] The plaintext to encrypt
122
+ # @return [String] The encrypted and encoded text
123
+ # @raise [RuntimeError] if text is nil
124
+ # @example Encrypt with Base58 encoding
125
+ # codec = GLogin::Codec.new('secret')
126
+ # encrypted = codec.encrypt('sensitive data')
127
+ # # => "3Hs9k2LgU..." (Base58 encoded)
128
+ #
129
+ # @example Encrypt with Base64 encoding
130
+ # codec = GLogin::Codec.new('secret', base64: true)
131
+ # encrypted = codec.encrypt('sensitive data')
132
+ # # => "U29tZUJhc2U2NC..." (Base64 encoded)
133
+ #
134
+ # @example Multiple encryptions produce different results
135
+ # codec = GLogin::Codec.new('secret')
136
+ # enc1 = codec.encrypt('hello')
137
+ # enc2 = codec.encrypt('hello')
138
+ # enc1 != enc2 # => true (due to random salt)
76
139
  def encrypt(text)
77
140
  raise 'Text can\'t be nil' if text.nil?
78
141
  if @secret.empty?
@@ -84,7 +147,7 @@ module GLogin
84
147
  salt = SecureRandom.base64(Random.rand(8..32))
85
148
  encrypted = cpr.update("#{salt} #{text}")
86
149
  encrypted << cpr.final
87
- @base64 ? Base64.encode64(encrypted).gsub("\n", '') : Base58.binary_to_base58(encrypted)
150
+ @base64 ? Base64.encode64(encrypted).delete("\n") : Base58.binary_to_base58(encrypted)
88
151
  end
89
152
  end
90
153