otp-jwt 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16fc6a7023ef65ec63c539e7a43ad48b6a5a8a64b41269687c8c1db53e321c4b
4
- data.tar.gz: 4c7e06c0dd6e1b29ea12de1f75c4f25310c8557965c7d8bbdeaf294c244683c2
3
+ metadata.gz: 4e4d38ef8b50541c6606645a9f75229e2599e47ec92323637886532853da6baa
4
+ data.tar.gz: 582df0752fead7e8b96615107a3d2db80a68d87b4fa2d533d6eaf9e2b7aaf035
5
5
  SHA512:
6
- metadata.gz: 7ee896f4d70eb673ce977a8db560cc8f193255802f36da5074791b883cfa453045a5e57c98f98ad919735eee0bbd191025e12cdaa253447f7b5fae71aed429a3
7
- data.tar.gz: b9a82e89ba20bf80d5e7ebfe3d292452d8fa487b5f3645ea3b94862f77078386c20f77ac97cecdfd5e9740a1aee592480ab53dd6e5c6d50f7cbcf423cd1aea90
6
+ metadata.gz: 89b0dc892cc73c5a863084a2017f1a23d621cd026a63c75256bb9be6642a909c03ed91be033e74fa28643af2453e662b12eb9ca3a0cbc35530de167348895fe6
7
+ data.tar.gz: 9e5b016cd192f8d5274257b1173a44868a5378b1cbb05f8e4de93136be887df6a623956054d756be6bd60c1fc30406d818713fee1285775bce5b2e39f990435b
data/README.md CHANGED
@@ -10,7 +10,7 @@ One time password (email, SMS) authentication support for HTTP APIs.
10
10
  This project provides a couple of mixins to help you build
11
11
  applications/HTTP APIs without asking your users to provide passwords.
12
12
 
13
- [Your browser probably can work seamlessly with OTPs](https://web.dev/web-otp/)!!! :heart_eyes:
13
+ [Your browser probably can work seamlessly with OTPs](https://web.dev/web-otp/)!!! :heart_eyes:
14
14
 
15
15
  ## About
16
16
 
@@ -40,6 +40,16 @@ and [JWT](https://github.com/jwt/ruby-jwt/).
40
40
 
41
41
  Thanks to everyone who worked on these amazing projects!
42
42
 
43
+
44
+ ## Sponsors
45
+
46
+ I'm grateful for the following companies for supporting this project!
47
+
48
+ <p align="center">
49
+ <a href="https://www.luneteyewear.com"><img src="https://user-images.githubusercontent.com/112147/136836142-2bfba96e-447f-4eb6-b137-2445aee81b37.png"/></a>
50
+ <a href="https://www.startuplandia.io"><img src="https://user-images.githubusercontent.com/112147/136836147-93f8ab17-2465-4477-a7ab-e38255483c66.png"/></a>
51
+ </p>
52
+
43
53
  ## Installation
44
54
 
45
55
  Add this line to your application's Gemfile:
@@ -77,6 +87,12 @@ require 'otp'
77
87
  # To load the JWT related support.
78
88
  require 'otp/jwt'
79
89
 
90
+ # Set to 'none' to disable verification at all.
91
+ # OTP::JWT::Token.jwt_algorithm = 'HS256'
92
+
93
+ # How long the token will be valid.
94
+ # OTP::JWT::Token.jwt_lifetime = 60 * 60 * 24
95
+
80
96
  OTP::JWT::Token.jwt_signature_key = ENV['YOUR-SIGN-KEY']
81
97
  ```
82
98
  ### OTP for Active Record models
@@ -99,6 +115,7 @@ one time passwords:
99
115
  This concern expects two attributes to be provided by the model, the:
100
116
  * `otp_secret`: of type string, used to store the OTP signature key
101
117
  * `otp_counter`: of type integer, used to store the OTP counter
118
+ * `expire_jwt_at`: of type datetime, **optional** and used to force a token to expire
102
119
 
103
120
  A migration to add these two looks like this:
104
121
  ```
@@ -111,6 +128,13 @@ User.all.each do |u|
111
128
  u.save()
112
129
  end
113
130
  ```
131
+
132
+ ##### Force a token to expire
133
+
134
+ If there's an `expire_jwt_at` value that is in the past, the user token will
135
+ be reset and it will require a new authentication to receive a working token.
136
+
137
+ This is handy if the user access needs to be scheduled and/or removed.
114
138
  #### Mailer support
115
139
 
116
140
  You can use the built-in mailer to deliver the OTP, just require it and
@@ -304,4 +328,5 @@ contributors are expected to adhere to the
304
328
 
305
329
  ## License
306
330
 
307
- TBD
331
+ The gem is available as open source under the terms of the
332
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -17,6 +17,7 @@ module OTP
17
17
  val = payload[claim_name]
18
18
  pk_col = self.column_for_attribute(self.primary_key)
19
19
 
20
+
20
21
  # Arel casts the values to the primary key type,
21
22
  # which means that an UUID becomes an integer by default...
22
23
  if self.connection.respond_to?(:lookup_cast_type_from_column)
@@ -28,7 +29,7 @@ module OTP
28
29
 
29
30
  return if casted_val.to_s != val.to_s.strip
30
31
 
31
- self.find_by(self.primary_key => val)
32
+ self.find_by(self.primary_key => val).expire_jwt?
32
33
  end
33
34
  end
34
35
  end
@@ -43,6 +44,17 @@ module OTP
43
44
  **(claims || {})
44
45
  )
45
46
  end
47
+
48
+ # Reset the [JWT] token if it is set to expire
49
+ #
50
+ # This method allows you to expire any token, independently from
51
+ # the JWT flags/payload.
52
+ #
53
+ # @return nil if the expiration worked, otherwise returns the model
54
+ def expire_jwt?
55
+ return self unless self.respond_to?(:expire_jwt_at)
56
+ return self unless expire_jwt_at? && expire_jwt_at.past?
57
+ end
46
58
  end
47
59
  end
48
60
  end
data/lib/otp/jwt/token.rb CHANGED
@@ -37,7 +37,10 @@ module OTP
37
37
  #
38
38
  # @return [Hash], JWT token payload
39
39
  def self.verify(token, opts = nil)
40
- ::JWT.decode(token.to_s, self.jwt_signature_key, true, opts || {})
40
+ verify = self.jwt_algorithm != 'none'
41
+ opts ||= { algorithm: self.jwt_algorithm }
42
+
43
+ ::JWT.decode(token.to_s, self.jwt_signature_key, verify, opts)
41
44
  end
42
45
 
43
46
  # Decodes a valid token into [Hash]
@@ -1,5 +1,5 @@
1
1
  module OTP
2
2
  module JWT
3
- VERSION = '0.2.7'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
data/spec/dummy.rb CHANGED
@@ -32,6 +32,7 @@ ActiveRecord::Schema.define do
32
32
  t.string :otp_secret
33
33
  t.integer :otp_counter
34
34
  t.timestamp :last_login_at
35
+ t.timestamp :expire_jwt_at
35
36
  t.timestamps
36
37
  end
37
38
  end
@@ -12,6 +12,18 @@ RSpec.describe OTP::JWT::Token, type: :model do
12
12
 
13
13
  describe '#sign' do
14
14
  it { expect(described_class.sign(payload)).to eq(token) }
15
+
16
+ context 'with the none algorithm' do
17
+ before do
18
+ OTP::JWT::Token.jwt_algorithm = 'none'
19
+ end
20
+
21
+ after do
22
+ OTP::JWT::Token.jwt_algorithm = 'HS256'
23
+ end
24
+
25
+ it { expect(described_class.sign(payload)).to eq(token) }
26
+ end
15
27
  end
16
28
 
17
29
  describe '#verify' do
@@ -31,6 +43,22 @@ RSpec.describe OTP::JWT::Token, type: :model do
31
43
  expect { described_class.verify(token) }
32
44
  .to raise_error(JWT::ExpiredSignature)
33
45
  end
46
+
47
+ context 'with an RSA key' do
48
+ before do
49
+ OTP::JWT::Token.jwt_signature_key = OpenSSL::PKey::RSA.new(2048)
50
+ OTP::JWT::Token.jwt_algorithm = 'RS256'
51
+ end
52
+
53
+ after do
54
+ OTP::JWT::Token.jwt_signature_key = '_'
55
+ OTP::JWT::Token.jwt_algorithm = 'HS256'
56
+ end
57
+
58
+ it do
59
+ expect(described_class.verify(token).first).to include(payload)
60
+ end
61
+ end
34
62
  end
35
63
 
36
64
  describe '#decode' do
@@ -50,5 +78,21 @@ RSpec.describe OTP::JWT::Token, type: :model do
50
78
  expect(described_class.decode(token)).to eq(nil)
51
79
  end
52
80
  end
81
+
82
+ context 'with the none algorithm' do
83
+ before do
84
+ OTP::JWT::Token.jwt_algorithm = 'none'
85
+ end
86
+
87
+ after do
88
+ OTP::JWT::Token.jwt_algorithm = 'HS256'
89
+ end
90
+
91
+ it do
92
+ expect(
93
+ described_class.decode(token) { |p| User.find(p['sub']) }
94
+ ).to eq(user)
95
+ end
96
+ end
53
97
  end
54
98
  end
data/spec/spec_helper.rb CHANGED
@@ -23,11 +23,12 @@ module OTP::JWT::FactoryHelpers
23
23
  # Creates an user
24
24
  #
25
25
  # @return [User]
26
- def create_user
26
+ def create_user(attrs = {})
27
27
  User.create!(
28
28
  full_name: FFaker::Name.name,
29
29
  email: FFaker::Internet.email,
30
- phone_number: FFaker::PhoneNumber.phone_number
30
+ phone_number: FFaker::PhoneNumber.phone_number,
31
+ **attrs
31
32
  )
32
33
  end
33
34
  end
@@ -13,6 +13,24 @@ RSpec.describe UsersController, type: :request do
13
13
  let(:user) { create_user }
14
14
 
15
15
  it { expect(response).to have_http_status(:ok) }
16
+
17
+ context 'with the expiration for JWT in future' do
18
+ let(:user) { create_user(expire_jwt_at: DateTime.tomorrow) }
19
+
20
+ it do
21
+ expect(response).to have_http_status(:ok)
22
+ expect(user.reload.expire_jwt_at).not_to be_nil
23
+ end
24
+ end
25
+
26
+ context 'with the expiration past for JWT' do
27
+ let(:user) { create_user(expire_jwt_at: DateTime.yesterday) }
28
+
29
+ it do
30
+ expect(response).to have_http_status(:unauthorized)
31
+ expect(user.reload.expire_jwt_at).not_to be_nil
32
+ end
33
+ end
16
34
  end
17
35
 
18
36
  context 'with bad subject' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otp-jwt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stas Suscov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-15 00:00:00.000000000 Z
11
+ date: 2021-10-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -250,7 +250,7 @@ homepage: https://github.com/stas/otp-jwt
250
250
  licenses:
251
251
  - MIT
252
252
  metadata: {}
253
- post_install_message:
253
+ post_install_message:
254
254
  rdoc_options: []
255
255
  require_paths:
256
256
  - lib
@@ -265,8 +265,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
265
  - !ruby/object:Gem::Version
266
266
  version: '0'
267
267
  requirements: []
268
- rubygems_version: 3.2.3
269
- signing_key:
268
+ rubygems_version: 3.2.22
269
+ signing_key:
270
270
  specification_version: 4
271
271
  summary: Passwordless HTTP APIs
272
272
  test_files: []