rotp 6.3.1 → 6.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c2b1404b98062478370f77b91348c5480783bbf6353fcc4d1beea0603bea63f
4
- data.tar.gz: 2134d1e2ce2af29a4b72f75a8c7b8bea073b215f2ace05be9e3cefd2f10e7587
3
+ metadata.gz: 19178f891005be16f5ade5f33a43c8cc77a38e1ccdd81ca62285b32cc2dd3090
4
+ data.tar.gz: 40f54eaf3c58f09af3c9fe55aa232c97a49a4df581f3293f04bd906f89f1df06
5
5
  SHA512:
6
- metadata.gz: 1b787d125aa066e36a2f905bd9e707253c77f75a8abe189ca6eb6f160fa4203960064089ec6ba15ea3e16d83826d6f2346528b95942681226fca32b47a09763a
7
- data.tar.gz: 1396bb30353567e42df1ecd602b6c93c88034d9755be47a1848b70200ceb1111cf35fcad0abd8c5d44881563821ae3d555be222153141da6723b1648072895c3
6
+ metadata.gz: f2ad58aa47f5781b2a15891a1cad38a3cab52e9b93a6e4b7ade3e0d18bce1e887d25369f3fdfd11e0752992cd78f66fccf2cc2cefcdb83671af4d73776eb3d3d
7
+ data.tar.gz: e3f243779f0dab586cb8307ebc40fed5ed4572d314b056a766a00baaa949971e085fb05557c23947715d5205e0048b0162554d69392e4772304e611fdceee8ce
@@ -2,35 +2,77 @@ name: Release
2
2
 
3
3
  on:
4
4
  push:
5
- branches:
5
+ branches:
6
6
  - 'main'
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag:
10
+ description: 'Existing tag to build and publish (e.g. v6.3.1). Used to backfill a release that never reached RubyGems.'
11
+ required: false
12
+ type: string
13
+ skip_tests:
14
+ description: 'Skip the test suite before publishing (only for backfilling an already-tagged release).'
15
+ required: false
16
+ default: false
17
+ type: boolean
18
+
19
+ permissions:
20
+ contents: write
21
+ pull-requests: write
22
+
7
23
  jobs:
8
- release:
24
+ release-please:
25
+ if: ${{ github.event_name == 'push' }}
9
26
  runs-on: ubuntu-latest
27
+ outputs:
28
+ release_created: ${{ steps.release.outputs.release_created }}
29
+ tag_name: ${{ steps.release.outputs.tag_name }}
10
30
  steps:
11
- - uses: google-github-actions/release-please-action@v4
31
+ - uses: googleapis/release-please-action@v4
12
32
  id: release
13
33
  with:
14
- release-type: ruby
15
- package-name: rotp
16
- version-file: "lib/rotp/version.rb"
17
- # Checkout code if release was created
18
- - uses: actions/checkout@v4
19
- if: ${{ steps.release.outputs.release_created }}
20
- # Setup ruby if a release was created
21
- - uses: ruby/setup-ruby@v1
34
+ config-file: release-please-config.json
35
+ manifest-file: .release-please-manifest.json
36
+
37
+ publish:
38
+ needs: [release-please]
39
+ if: ${{ always() && ((github.event_name == 'workflow_dispatch' && inputs.tag != '') || needs.release-please.outputs.release_created == 'true') }}
40
+ runs-on: ubuntu-latest
41
+ timeout-minutes: 20
42
+ permissions:
43
+ contents: write
44
+ id-token: write
45
+ env:
46
+ TAG: ${{ inputs.tag || needs.release-please.outputs.tag_name }}
47
+ steps:
48
+ - uses: actions/checkout@v6
49
+ with:
50
+ ref: ${{ env.TAG }}
51
+ fetch-depth: 0
52
+ persist-credentials: false
53
+ - name: Set up Ruby
54
+ uses: ruby/setup-ruby@v1
22
55
  with:
23
- ruby-version: 3.2
24
- bundler-cache: true
25
- if: ${{ steps.release.outputs.release_created }}
56
+ ruby-version: '3.3'
57
+ bundler-cache: ${{ !inputs.skip_tests }}
58
+ - name: Verify gem version matches tag
59
+ run: |
60
+ tag_version="${TAG#v}"
61
+ gem_version="$(ruby -Ilib -e "require 'rotp/version'; print ROTP::VERSION")"
62
+ echo "tag=$tag_version gem=$gem_version"
63
+ test "$tag_version" = "$gem_version"
26
64
  - name: Run tests
65
+ if: ${{ !inputs.skip_tests }}
27
66
  run: bundle exec rspec
28
- if: ${{ steps.release.outputs.release_created }}
29
- # build gem and add to release
30
- - name: Upload Release Artifact
31
- if: ${{ steps.release.outputs.release_created }}
67
+ - name: Configure RubyGems credentials (trusted publishing)
68
+ uses: rubygems/configure-rubygems-credentials@v2.1.0
69
+ - name: Build and publish gem
70
+ run: |
71
+ gem build *.gemspec
72
+ gem push *.gem
73
+ - name: Attach gem to the GitHub release
32
74
  env:
33
75
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34
- run:
35
- gem build *.gemspec
36
- gh release upload ${{ steps.release.outputs.tag_name }} *.gem
76
+ run: |
77
+ gh release view "$TAG" >/dev/null 2>&1 || gh release create "$TAG" --title "$TAG" --generate-notes
78
+ gh release upload "$TAG" *.gem --clobber
@@ -12,11 +12,10 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
 
14
14
  strategy:
15
- # Include '3.5.0-preview1' to test compatibility with the upcoming Ruby 3.5 release.
16
15
  matrix:
17
- ruby-version: ['3.5.0-preview1', '3.4', '3.3', '3.2', '3.0', '2.7', '2.3', 'truffleruby-head']
16
+ ruby-version: ['4.0', '3.4', '3.3', '3.2', '3.0', '2.7', '2.3', 'truffleruby-head']
18
17
  steps:
19
- - uses: actions/checkout@v4
18
+ - uses: actions/checkout@v6
20
19
  - name: Set up Ruby ${{ matrix.ruby-version }}
21
20
  uses: ruby/setup-ruby@v1
22
21
  with:
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "6.3.0"
2
+ ".": "6.4.0"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [6.4.0](https://github.com/mdp/rotp/compare/v6.3.1...v6.4.0) (2026-10-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * add skip_default_uri_params option to provisioning URIs ([16e873c](https://github.com/mdp/rotp/commit/16e873c68e0736f8ce019252985ad0e9fcce95f1))
9
+
3
10
  ## [6.3.1] (2025-06-03)
4
11
 
5
12
  * Fix: Drop usage of OpenStruct to support Ruby 3.5
data/README.md CHANGED
@@ -8,7 +8,7 @@ Although this library will continue to be maintained, if you're implementing a 2
8
8
  - [Webauthn Ruby Gem](https://github.com/cedarcode/webauthn-ruby)
9
9
  - [Rails demo app with Webauthn](https://github.com/cedarcode/webauthn-rails-demo-app)
10
10
 
11
- ----
11
+ ---
12
12
 
13
13
  # The Ruby One Time Password Library
14
14
 
@@ -17,7 +17,6 @@ Although this library will continue to be maintained, if you're implementing a 2
17
17
  [![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://www.rubydoc.info/github/mdp/rotp/master)
18
18
  [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/mdp/rotp/blob/master/LICENSE)
19
19
 
20
-
21
20
  A ruby library for generating and validating one time passwords (HOTP & TOTP) according to [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) and [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238).
22
21
 
23
22
  ROTP is compatible with [Google Authenticator](https://github.com/google/google-authenticator) available for [Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) and [iPhone](https://itunes.apple.com/en/app/google-authenticator/id388497605) and any other TOTP based implementations.
@@ -150,6 +149,10 @@ totp.provisioning_uri("alice@google.com") # => 'otpauth://totp/My%20Service:alic
150
149
 
151
150
  hotp = ROTP::HOTP.new("base32secret3232", issuer: "My Service")
152
151
  hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service&counter=0'
152
+
153
+ # By default optional values will be skipped from URI. But you can force to specify all values in the URI by passing `skip_default_uri_params: false`
154
+ totp = ROTP::TOTP.new("base32secret3232", issuer: "My Service", skip_default_uri_params: false)
155
+ totp.provisioning_uri("alice@google.com") # => otpauth://totp/My%20Service:alice%40google.com?secret=base32secret3232&issuer=My%20Service&algorithm=SHA1&digits=6&period=30
153
156
  ```
154
157
 
155
158
  This can then be rendered as a QR Code which the user can scan using their mobile phone and the appropriate application.
data/lib/rotp/otp/uri.rb CHANGED
@@ -15,7 +15,7 @@ module ROTP
15
15
  private
16
16
 
17
17
  def algorithm
18
- return unless %w[sha256 sha512].include?(@otp.digest)
18
+ return if skip_default_uri_params && @otp.digest == DEFAULT_DIGEST
19
19
 
20
20
  @otp.digest.upcase
21
21
  end
@@ -28,7 +28,7 @@ module ROTP
28
28
  end
29
29
 
30
30
  def digits
31
- return if @otp.digits == DEFAULT_DIGITS
31
+ return if skip_default_uri_params && @otp.digits == DEFAULT_DIGITS
32
32
 
33
33
  @otp.digits
34
34
  end
@@ -62,7 +62,7 @@ module ROTP
62
62
 
63
63
  def period
64
64
  return if @otp.is_a?(HOTP)
65
- return if @otp.interval == DEFAULT_INTERVAL
65
+ return if skip_default_uri_params && @otp.interval == DEFAULT_INTERVAL
66
66
 
67
67
  @otp.interval
68
68
  end
@@ -73,6 +73,10 @@ module ROTP
73
73
  when HOTP then 'hotp'
74
74
  end
75
75
  end
76
+
77
+ def skip_default_uri_params
78
+ @otp.skip_default_uri_params
79
+ end
76
80
  end
77
81
  end
78
82
  end
data/lib/rotp/otp.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  module ROTP
2
2
  class OTP
3
- attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params
3
+ attr_reader :secret, :digits, :digest, :name, :issuer, :provisioning_params, :skip_default_uri_params
4
4
  DEFAULT_DIGITS = 6
5
+ DEFAULT_DIGEST = 'sha1'
5
6
 
6
7
  # @param [String] secret in the form of base32
7
8
  # @option options digits [Integer] (6)
@@ -19,13 +20,18 @@ module ROTP
19
20
  # @option options provisioning_params [Hash] ({})
20
21
  # Additional non-standard params you may want appended to the
21
22
  # provisioning URI. Ex. `image: 'https://example.com/icon.png'`
23
+ # @option options skip_default_uri_params [Boolean] (true)
24
+ # When true, parameters that match the RFC defaults (SHA1, 6 digits,
25
+ # 30 second period) are omitted from the provisioning URI. Set to
26
+ # false to always include them.
22
27
  # @returns [OTP] OTP instantiation
23
28
  def initialize(s, options = {})
24
29
  @digits = options[:digits] || DEFAULT_DIGITS
25
- @digest = options[:digest] || 'sha1'
30
+ @digest = options[:digest] || DEFAULT_DIGEST
26
31
  @name = options[:name]
27
32
  @issuer = options[:issuer]
28
33
  @provisioning_params = options[:provisioning_params] || {}
34
+ @skip_default_uri_params = options.fetch(:skip_default_uri_params, true)
29
35
  @secret = s
30
36
  end
31
37
 
data/lib/rotp/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ROTP
2
- VERSION = '6.3.1'.freeze
2
+ VERSION = '6.4.0'.freeze
3
3
  end
@@ -1,6 +1,9 @@
1
1
  {
2
+ "release-type": "ruby",
2
3
  "packages": {
3
4
  ".": {
5
+ "release-type": "ruby",
6
+ "version-file": "lib/rotp/version.rb",
4
7
  "changelog-path": "CHANGELOG.md",
5
8
  "bump-minor-pre-major": false,
6
9
  "bump-patch-for-minor-pre-major": false,
@@ -9,4 +12,4 @@
9
12
  }
10
13
  },
11
14
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
12
- }
15
+ }
@@ -110,7 +110,7 @@ RSpec.describe ROTP::HOTP do
110
110
 
111
111
  describe '#provisioning_uri' do
112
112
  let(:hotp) { ROTP::HOTP.new('a' * 32, name: "m@mdp.im") }
113
- let(:params) { CGI.parse URI.parse(uri).query }
113
+ let(:params) { query_params_for(uri) }
114
114
 
115
115
  it 'created from the otp instance data' do
116
116
  expect(hotp.provisioning_uri())
@@ -37,6 +37,12 @@ RSpec.describe ROTP::OTP::URI do
37
37
  expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA512'
38
38
  end
39
39
 
40
+ it 'includes an uppercase non-default digest' do
41
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'SHA256')
42
+ uri = described_class.new(otp, account_name: 'alice@google.com')
43
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&algorithm=SHA256'
44
+ end
45
+
40
46
  it 'includes non-default 8 digits' do
41
47
  otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 8)
42
48
  uri = described_class.new(otp, account_name: 'alice@google.com')
@@ -96,4 +102,37 @@ RSpec.describe ROTP::OTP::URI do
96
102
  uri = described_class.new(otp, account_name: 'alice+1234@google.com')
97
103
  expect(uri.to_s).to eq 'otpauth://totp/alice%2B1234%40google.com?secret=JBSWY3DPEHPK3PXP'
98
104
  end
105
+
106
+ context 'with skip_default_uri_params' do
107
+ it 'excludes default SHA1 algorithm' do
108
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digest: 'sha1', skip_default_uri_params: true)
109
+ uri = described_class.new(otp, account_name: 'alice@google.com')
110
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP'
111
+ end
112
+
113
+ it 'excludes default 6 digits' do
114
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', digits: 6, skip_default_uri_params: true)
115
+ uri = described_class.new(otp, account_name: 'alice@google.com')
116
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP'
117
+ end
118
+
119
+ it 'excludes default 30 second period' do
120
+ otp = ROTP::TOTP.new('JBSWY3DPEHPK3PXP', interval: 30, skip_default_uri_params: true)
121
+ uri = described_class.new(otp, account_name: 'alice@google.com')
122
+ expect(uri.to_s).to eq 'otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP'
123
+ end
124
+
125
+ it 'includes all parameters when skip_default_uri_params is false' do
126
+ otp = ROTP::TOTP.new(
127
+ 'JBSWY3DPEHPK3PXP',
128
+ digest: 'sha1',
129
+ digits: 6,
130
+ interval: 30,
131
+ issuer: 'ACME Co',
132
+ skip_default_uri_params: false
133
+ )
134
+ uri = described_class.new(otp, account_name: 'alice@google.com')
135
+ expect(uri.to_s).to eq 'otpauth://totp/ACME%20Co:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30'
136
+ end
137
+ end
99
138
  end
@@ -223,7 +223,7 @@ RSpec.describe ROTP::TOTP do
223
223
 
224
224
 
225
225
  describe '#provisioning_uri' do
226
- let(:params) { CGI.parse URI.parse(uri).query }
226
+ let(:params) { query_params_for(uri) }
227
227
 
228
228
  context "with a provided name on the TOTP instance" do
229
229
  let(:totp) { ROTP::TOTP.new(TEST_SECRET, name: "m@mdp.im") }
data/spec/spec_helper.rb CHANGED
@@ -3,6 +3,7 @@ SimpleCov.start do
3
3
  add_filter '/spec/'
4
4
  end
5
5
 
6
+ require 'uri'
6
7
  require 'rotp'
7
8
  require 'timecop'
8
9
 
@@ -17,4 +18,10 @@ RSpec.configure do |config|
17
18
  end
18
19
  end
19
20
 
21
+ def query_params_for(uri)
22
+ URI.decode_www_form(URI.parse(uri).query).each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |(key, value), params|
23
+ params[key] << value
24
+ end
25
+ end
26
+
20
27
  require_relative '../lib/rotp'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rotp
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Percival