otp-jwt 0.2.4 → 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
- SHA1:
3
- metadata.gz: 5a6047b41e5b59f65c9a0ab0b138798ba611a113
4
- data.tar.gz: 5b157ecb5af5abebcd324f3b18698279af02d050
2
+ SHA256:
3
+ metadata.gz: 4e4d38ef8b50541c6606645a9f75229e2599e47ec92323637886532853da6baa
4
+ data.tar.gz: 582df0752fead7e8b96615107a3d2db80a68d87b4fa2d533d6eaf9e2b7aaf035
5
5
  SHA512:
6
- metadata.gz: 3c9ee97ec926b861d0599462def570ae799ac65a3f59040725b376359de451817680b1a538759126fe698895157813b15a911738c4976dd801cce5d17aa0a562
7
- data.tar.gz: d3fede4d63a6121e34fdcad1b8d3c48cd85088e99bfa94b001cf94895ea6574e66e061db7e442a9b4904eb079c7457cd0398d897eaf0085b0d29b50cb3428655
6
+ metadata.gz: 89b0dc892cc73c5a863084a2017f1a23d621cd026a63c75256bb9be6642a909c03ed91be033e74fa28643af2453e662b12eb9ca3a0cbc35530de167348895fe6
7
+ data.tar.gz: 9e5b016cd192f8d5274257b1173a44868a5378b1cbb05f8e4de93136be887df6a623956054d756be6bd60c1fc30406d818713fee1285775bce5b2e39f990435b
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Stas Suscov
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
13
+ all 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
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -10,6 +10,8 @@ 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:
14
+
13
15
  ## About
14
16
 
15
17
  The goal of this project is to provide support for one time passwords
@@ -38,6 +40,16 @@ and [JWT](https://github.com/jwt/ruby-jwt/).
38
40
 
39
41
  Thanks to everyone who worked on these amazing projects!
40
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
+
41
53
  ## Installation
42
54
 
43
55
  Add this line to your application's Gemfile:
@@ -75,6 +87,12 @@ require 'otp'
75
87
  # To load the JWT related support.
76
88
  require 'otp/jwt'
77
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
+
78
96
  OTP::JWT::Token.jwt_signature_key = ENV['YOUR-SIGN-KEY']
79
97
  ```
80
98
  ### OTP for Active Record models
@@ -97,12 +115,26 @@ one time passwords:
97
115
  This concern expects two attributes to be provided by the model, the:
98
116
  * `otp_secret`: of type string, used to store the OTP signature key
99
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
100
119
 
101
120
  A migration to add these two looks like this:
102
121
  ```
103
122
  $ rails g migration add_otp_to_users otp_secret:string otp_counter:integer
104
123
  ```
105
124
 
125
+ Generate `opt_secret` by running the following in rails console if you have preexisting user data:
126
+ ```
127
+ User.all.each do |u|
128
+ u.save()
129
+ end
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.
106
138
  #### Mailer support
107
139
 
108
140
  You can use the built-in mailer to deliver the OTP, just require it and
@@ -296,4 +328,5 @@ contributors are expected to adhere to the
296
328
 
297
329
  ## License
298
330
 
299
- TBD
331
+ The gem is available as open source under the terms of the
332
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -36,7 +36,8 @@ module OTP
36
36
  def verify_otp(otp)
37
37
  return nil if !valid? || !persisted? || otp_secret.blank?
38
38
 
39
- hotp = ROTP::HOTP.new(otp_secret, digits: OTP_DIGITS)
39
+ otp_digits = self.class.const_get(:OTP_DIGITS)
40
+ hotp = ROTP::HOTP.new(otp_secret, digits: otp_digits)
40
41
  transaction do
41
42
  otp_status = hotp.verify(otp.to_s, otp_counter)
42
43
  increment!(:otp_counter)
@@ -72,7 +73,6 @@ module OTP
72
73
  end
73
74
 
74
75
  private
75
-
76
76
  # Provides a default value for the OTP secret attribute
77
77
  #
78
78
  # @return [String]
@@ -3,7 +3,6 @@ module OTP
3
3
  # [ActionController] concern.
4
4
  module ActionController
5
5
  private
6
-
7
6
  # Authenticates a model and responds with a [JWT] token
8
7
  #
9
8
  # @return [String] with authentication token and country shop ID.
@@ -17,13 +17,19 @@ module OTP
17
17
  val = payload[claim_name]
18
18
  pk_col = self.column_for_attribute(self.primary_key)
19
19
 
20
- # Arel casts the values to the primary key type, which means
21
- # that an UUID will become an integer...
22
- casted_val = self.connection.type_cast(val, pk_col)
20
+
21
+ # Arel casts the values to the primary key type,
22
+ # which means that an UUID becomes an integer by default...
23
+ if self.connection.respond_to?(:lookup_cast_type_from_column)
24
+ pk_type = self.connection.lookup_cast_type_from_column(pk_col)
25
+ casted_val = pk_type.serialize(val)
26
+ else
27
+ casted_val = self.connection.type_cast(val, pk_col)
28
+ end
23
29
 
24
30
  return if casted_val.to_s != val.to_s.strip
25
31
 
26
- self.find_by(self.primary_key => val)
32
+ self.find_by(self.primary_key => val).expire_jwt?
27
33
  end
28
34
  end
29
35
  end
@@ -38,6 +44,17 @@ module OTP
38
44
  **(claims || {})
39
45
  )
40
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
41
58
  end
42
59
  end
43
60
  end
@@ -15,7 +15,7 @@ module OTP
15
15
  #
16
16
  # @return [Hash] the authorization headers
17
17
  def jwt_auth_header(entity_or_subject)
18
- return json_headers unless entity_or_subject.present?
18
+ return json_headers if entity_or_subject.blank?
19
19
 
20
20
  token = entity_or_subject.try(:to_jwt)
21
21
  token ||= OTP::JWT::Token.sign(sub: entity_or_subject)
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.4'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
data/spec/dummy.rb ADDED
@@ -0,0 +1,83 @@
1
+ require 'active_record/railtie'
2
+ require 'action_controller/railtie'
3
+ require 'global_id/railtie'
4
+ require 'otp/mailer'
5
+
6
+ class Dummy < Rails::Application
7
+ secrets.secret_key_base = '_'
8
+
9
+ config.hosts << 'www.example.com' if config.respond_to?(:hosts)
10
+
11
+ config.logger = Logger.new($stdout)
12
+ config.logger.level = ENV['LOG_LEVEL'] || Logger::WARN
13
+
14
+ routes.draw do
15
+ resources :users, only: [:index]
16
+ resources :tokens, only: [:create]
17
+ end
18
+ end
19
+
20
+ GlobalID.app = Dummy
21
+ Rails.logger = Dummy.config.logger
22
+ ActiveRecord::Base.logger = Dummy.config.logger
23
+ ActiveRecord::Base.establish_connection(
24
+ ENV['DATABASE_URL'] || 'sqlite3::memory:'
25
+ )
26
+
27
+ ActiveRecord::Schema.define do
28
+ create_table :users, force: true do |t|
29
+ t.string :email
30
+ t.string :full_name
31
+ t.string :phone_number
32
+ t.string :otp_secret
33
+ t.integer :otp_counter
34
+ t.timestamp :last_login_at
35
+ t.timestamp :expire_jwt_at
36
+ t.timestamps
37
+ end
38
+ end
39
+
40
+ class User < ActiveRecord::Base
41
+ include GlobalID::Identification
42
+ include OTP::ActiveRecord
43
+ include OTP::JWT::ActiveRecord
44
+
45
+ def email_otp
46
+ OTP::Mailer.otp(email, otp, self).deliver_later
47
+ end
48
+ end
49
+
50
+ class ApplicationController < ActionController::Base
51
+ include OTP::JWT::ActionController
52
+
53
+ private
54
+ def current_user
55
+ @jwt_user ||= User.from_jwt(request_authorization_header)
56
+ end
57
+
58
+ def current_user!
59
+ current_user || raise('User authentication failed')
60
+ rescue
61
+ head(:unauthorized)
62
+ end
63
+ end
64
+
65
+ class UsersController < ApplicationController
66
+ before_action :current_user!
67
+
68
+ def index
69
+ render json: current_user
70
+ end
71
+ end
72
+
73
+ class TokensController < ApplicationController
74
+ def create
75
+ user = User.find_by(email: params[:email])
76
+
77
+ jwt_from_otp(user, params[:otp]) do |auth_user|
78
+ auth_user.update_column(:last_login_at, DateTime.current)
79
+
80
+ render json: { token: auth_user.to_jwt }, status: :created
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe OTP::JWT::Token, type: :model do
4
+ let(:payload) { { 'sub' => FFaker::Internet.password } }
5
+ let(:token) do
6
+ JWT.encode(
7
+ payload.dup.merge(exp: Time.now.to_i + described_class.jwt_lifetime),
8
+ described_class.jwt_signature_key,
9
+ described_class.jwt_algorithm
10
+ )
11
+ end
12
+
13
+ describe '#sign' do
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
27
+ end
28
+
29
+ describe '#verify' do
30
+ it do
31
+ expect(described_class.verify(token).first).to include(payload)
32
+ end
33
+
34
+ it 'with a bad token' do
35
+ expect { described_class.verify(FFaker::Internet.password) }
36
+ .to raise_error(JWT::DecodeError)
37
+ end
38
+
39
+ it 'with an expired token' do
40
+ token = OTP::JWT::Token.sign(
41
+ sub: FFaker::Internet.password, exp: DateTime.now.to_i
42
+ )
43
+ expect { described_class.verify(token) }
44
+ .to raise_error(JWT::ExpiredSignature)
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
62
+ end
63
+
64
+ describe '#decode' do
65
+ let(:user) { create_user }
66
+ let(:payload) { { 'sub' => user.id } }
67
+
68
+ it do
69
+ expect(
70
+ described_class.decode(token) { |p| User.find(p['sub']) }
71
+ ).to eq(user)
72
+ end
73
+
74
+ context 'with a bad token' do
75
+ let(:token) { FFaker::Internet.password }
76
+
77
+ it do
78
+ expect(described_class.decode(token)).to eq(nil)
79
+ end
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
97
+ end
98
+ end
@@ -0,0 +1,71 @@
1
+ require 'bundler/setup'
2
+ require 'simplecov'
3
+
4
+ SimpleCov.start do
5
+ add_group 'Lib', 'lib'
6
+ add_group 'Tests', 'spec'
7
+ end
8
+ SimpleCov.minimum_coverage 90
9
+
10
+ require 'otp'
11
+ require 'otp/jwt'
12
+ require 'otp/jwt/test_helpers'
13
+ require_relative 'dummy'
14
+ require 'ffaker'
15
+ require 'rspec/rails'
16
+
17
+ OTP::JWT::Token.jwt_signature_key = '_'
18
+ OTP::Mailer.default from: '_'
19
+ ActiveJob::Base.queue_adapter = :test
20
+ ActionMailer::Base.delivery_method = :test
21
+
22
+ module OTP::JWT::FactoryHelpers
23
+ # Creates an user
24
+ #
25
+ # @return [User]
26
+ def create_user(attrs = {})
27
+ User.create!(
28
+ full_name: FFaker::Name.name,
29
+ email: FFaker::Internet.email,
30
+ phone_number: FFaker::PhoneNumber.phone_number,
31
+ **attrs
32
+ )
33
+ end
34
+ end
35
+
36
+ module Rails4RequestMethods
37
+ [:get, :post, :put, :delete].each do |method_name|
38
+ define_method(method_name) do |path, named_args|
39
+ super(
40
+ path,
41
+ named_args.delete(:params),
42
+ named_args.delete(:headers)
43
+ )
44
+ end
45
+ end
46
+ end
47
+
48
+ RSpec.configure do |config|
49
+ config.use_transactional_fixtures = true
50
+ config.mock_with :rspec
51
+ config.filter_run_when_matching :focus
52
+ config.disable_monkey_patching!
53
+
54
+ config.expect_with :rspec do |c|
55
+ c.syntax = :expect
56
+ end
57
+
58
+ config.include OTP::JWT::TestHelpers, type: :model
59
+ config.include OTP::JWT::FactoryHelpers, type: :model
60
+ config.include ActiveJob::TestHelper, type: :model
61
+
62
+ config.include OTP::JWT::TestHelpers, type: :request
63
+ config.include OTP::JWT::FactoryHelpers, type: :request
64
+ config.include ActiveJob::TestHelper, type: :request
65
+ config.include Dummy.routes.url_helpers, type: :request
66
+
67
+ if ::Rails::VERSION::MAJOR == 4
68
+ config.include Rails4RequestMethods, type: :request
69
+ config.include Rails4RequestMethods, type: :controller
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe TokensController, type: :request do
4
+ let(:user) { create_user }
5
+ let(:params) { }
6
+
7
+ around do |examp|
8
+ perform_enqueued_jobs(&examp)
9
+ end
10
+
11
+ before do
12
+ ActionMailer::Base.deliveries.clear
13
+ ActiveJob::Base.queue_adapter.performed_jobs.clear
14
+ post(tokens_path, params: params.to_json, headers: json_headers)
15
+ end
16
+
17
+ it { expect(response).to have_http_status(:forbidden) }
18
+
19
+ context 'with good email and no otp' do
20
+ let(:params) { { email: user.email } }
21
+
22
+ it do
23
+ expect(response).to have_http_status(:bad_request)
24
+
25
+ mail = ActionMailer::Base.deliveries.last
26
+ expect(mail.subject).to eq(OTP::Mailer.default[:subject])
27
+ end
28
+ end
29
+
30
+ context 'with good email and bad otp' do
31
+ let(:params) { { email: user.email, otp: FFaker::Internet.password } }
32
+
33
+ it do
34
+ expect(response).to have_http_status(:forbidden)
35
+ expect(ActionMailer::Base.deliveries.size).to eq(0)
36
+ end
37
+ end
38
+
39
+ context 'with good email and good otp' do
40
+ let(:params) { { email: user.email, otp: user.otp } }
41
+
42
+ it do
43
+ expect(response).to have_http_status(:created)
44
+ expect(User.from_jwt(response_json['token'])).to eq(user)
45
+ expect(ActionMailer::Base.deliveries.size).to eq(0)
46
+
47
+ expect(user.reload.last_login_at).not_to be_blank
48
+ end
49
+ end
50
+ end
data/spec/user_spec.rb ADDED
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe User, type: :model do
4
+ let(:user) { create_user }
5
+
6
+ it { expect(User.new.otp_secret).not_to be_blank }
7
+ it { expect(User.new.deliver_otp).to be_blank }
8
+ it { expect(User.new.otp).to be_blank }
9
+
10
+ describe '#from_jwt' do
11
+ let(:token) { user.to_jwt }
12
+
13
+ it do
14
+ expect(User.from_jwt(token)).to eq(user)
15
+ end
16
+
17
+ context 'with a cast-able subject value' do
18
+ let(:token) { OTP::JWT::Token.sign(sub: user.id.to_s + '_text') }
19
+
20
+ it do
21
+ expect(User.from_jwt(token)).to be_nil
22
+ end
23
+ end
24
+
25
+ context 'with a custom claim name' do
26
+ let(:claim_value) { FFaker::Internet.password }
27
+ let(:token) { user.to_jwt(my_claim_name: claim_value) }
28
+
29
+ it do
30
+ expect(OTP::JWT::Token.decode(token)['my_claim_name'])
31
+ .to eq(claim_value)
32
+ expect(User.from_jwt(token, 'my_claim_name')).to be_nil
33
+ end
34
+ end
35
+ end
36
+
37
+ describe '#otp' do
38
+ it do
39
+ expect { user.otp }.to change(user, :otp_counter).by(1)
40
+ end
41
+
42
+ context 'without a secret' do
43
+ it do
44
+ user.update_column(:otp_secret, nil)
45
+ expect(user.otp).to be_nil
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#verify_otp' do
51
+ it 'increments the otp counter after verification' do
52
+ expect(user.verify_otp(user.otp)).to be_truthy
53
+ expect { user.verify_otp(user.otp) }.to change(user, :otp_counter).by(2)
54
+ end
55
+
56
+ context 'without a secret' do
57
+ it do
58
+ user.update_column(:otp_secret, nil)
59
+ expect(user.verify_otp(rand(1000..2000).to_s)).to be_nil
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ let(:user) { nil }
5
+
6
+ before do
7
+ get(users_path, headers: jwt_auth_header(user))
8
+ end
9
+
10
+ it { expect(response).to have_http_status(:unauthorized) }
11
+
12
+ context 'with known subject token' do
13
+ let(:user) { create_user }
14
+
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
34
+ end
35
+
36
+ context 'with bad subject' do
37
+ let(:user) { FFaker::Internet.password }
38
+
39
+ it { expect(response).to have_http_status(:unauthorized) }
40
+ end
41
+ end
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.4
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: 2019-05-16 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
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 2.2.0.pre.beta.0
33
+ version: '2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 2.2.0.pre.beta.0
40
+ version: '2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rotp
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '4.1'
47
+ version: '6'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '4.1'
54
+ version: '6'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rubocop-performance
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +192,20 @@ dependencies:
178
192
  - - ">="
179
193
  - !ruby/object:Gem::Version
180
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: tzinfo-data
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
181
209
  - !ruby/object:Gem::Dependency
182
210
  name: yardstick
183
211
  requirement: !ruby/object:Gem::Requirement
@@ -199,14 +227,8 @@ executables: []
199
227
  extensions: []
200
228
  extra_rdoc_files: []
201
229
  files:
202
- - ".github/main.workflow"
203
- - ".gitignore"
204
- - ".rspec"
205
- - ".rubocop.yml"
206
- - ".yardstick.yml"
207
- - Gemfile
230
+ - LICENSE.txt
208
231
  - README.md
209
- - Rakefile
210
232
  - lib/otp.rb
211
233
  - lib/otp/active_record.rb
212
234
  - lib/otp/jwt.rb
@@ -218,12 +240,17 @@ files:
218
240
  - lib/otp/mailer.rb
219
241
  - lib/otp/mailer/otp.text.erb
220
242
  - lib/otp/sms_job.rb
221
- - otp-jwt.gemspec
243
+ - spec/dummy.rb
244
+ - spec/otp/jwt/token_spec.rb
245
+ - spec/spec_helper.rb
246
+ - spec/tokens_controller_spec.rb
247
+ - spec/user_spec.rb
248
+ - spec/users_controller_spec.rb
222
249
  homepage: https://github.com/stas/otp-jwt
223
250
  licenses:
224
- - TBD
251
+ - MIT
225
252
  metadata: {}
226
- post_install_message:
253
+ post_install_message:
227
254
  rdoc_options: []
228
255
  require_paths:
229
256
  - lib
@@ -238,9 +265,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
265
  - !ruby/object:Gem::Version
239
266
  version: '0'
240
267
  requirements: []
241
- rubyforge_project:
242
- rubygems_version: 2.5.2.2
243
- signing_key:
268
+ rubygems_version: 3.2.22
269
+ signing_key:
244
270
  specification_version: 4
245
271
  summary: Passwordless HTTP APIs
246
272
  test_files: []
@@ -1,44 +0,0 @@
1
- workflow "Tests" {
2
- on = "push"
3
- resolves = [
4
- "rspec-ruby2.6_rails4",
5
- "rspec-ruby2.6_rails5",
6
- "rspec-ruby2.6_rails6"
7
- ]
8
- }
9
-
10
- action "rspec-ruby2.6_rails4" {
11
- uses = "docker://ruby:2.6-alpine"
12
- env = {
13
- RAILS_VERSION = "~> 4"
14
- SQLITE3_VERSION = "~> 1.3.6"
15
- }
16
- args = [
17
- "sh", "-c",
18
- "apk add -U git build-base sqlite-dev && bundle install && rake"
19
- ]
20
- }
21
-
22
- action "rspec-ruby2.6_rails5" {
23
- uses = "docker://ruby:2.6-alpine"
24
- needs = ["rspec-ruby2.6_rails4"]
25
- env = {
26
- RAILS_VERSION = "~> 5"
27
- }
28
- args = [
29
- "sh", "-c",
30
- "apk add -U git build-base sqlite-dev && bundle install && rake"
31
- ]
32
- }
33
-
34
- action "rspec-ruby2.6_rails6" {
35
- uses = "docker://ruby:2.6-alpine"
36
- needs = ["rspec-ruby2.6_rails5"]
37
- env = {
38
- RAILS_VERSION = "~> 6.0.0.rc1"
39
- }
40
- args = [
41
- "sh", "-c",
42
- "apk add -U git build-base sqlite-dev && bundle install && rake"
43
- ]
44
- }
data/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- coverage
2
- pkg
3
- *.lock
4
- tmp
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,46 +0,0 @@
1
- inherit_gem:
2
- rubocop-rails_config:
3
- - config/rails.yml
4
-
5
- require:
6
- - rubocop-performance
7
- - rubocop-rspec
8
-
9
- Rails:
10
- Enabled: true
11
-
12
- RSpec:
13
- Enabled: true
14
-
15
- RSpec/MultipleExpectations:
16
- Enabled: false
17
-
18
- Performance:
19
- Enabled: true
20
-
21
- Bundler:
22
- Enabled: true
23
-
24
- Gemspec:
25
- Enabled: true
26
-
27
- Style/StringLiterals:
28
- Enabled: true
29
- EnforcedStyle: single_quotes
30
-
31
- Style/FrozenStringLiteralComment:
32
- Enabled: false
33
-
34
- Metrics/LineLength:
35
- Max: 80
36
-
37
- Metrics/BlockLength:
38
- Exclude:
39
- - 'spec/**/*_spec.rb'
40
- - '**/*.gemspec'
41
-
42
- Layout/IndentationConsistency:
43
- EnforcedStyle: normal
44
-
45
- Style/BlockDelimiters:
46
- Enabled: true
data/.yardstick.yml DELETED
@@ -1,29 +0,0 @@
1
- ---
2
- path: ['lib/**/*.rb']
3
- threshold: 100
4
- rules:
5
- ApiTag::Presence:
6
- enabled: false
7
- ApiTag::Inclusion:
8
- enabled: false
9
- ApiTag::ProtectedMethod:
10
- enabled: false
11
- ApiTag::PrivateMethod:
12
- enabled: false
13
- ExampleTag:
14
- enabled: false
15
- ReturnTag:
16
- enabled: true
17
- exclude: []
18
- Summary::Presence:
19
- enabled: true
20
- exclude: []
21
- Summary::Length:
22
- enabled: true
23
- exclude: []
24
- Summary::Delimiter:
25
- enabled: true
26
- exclude: []
27
- Summary::SingleLine:
28
- enabled: true
29
- exclude: []
data/Gemfile DELETED
@@ -1,7 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in otp-jwt.gemspec
4
- gemspec
5
-
6
- gem 'rails', ENV['RAILS_VERSION']
7
- gem 'tzinfo-data'
data/Rakefile DELETED
@@ -1,30 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
- require 'rubocop/rake_task'
4
- require 'yaml'
5
- require 'yardstick'
6
-
7
- desc('Documentation stats and measurements')
8
- task('qa:docs') do
9
- yaml = YAML.load_file(File.expand_path('../.yardstick.yml', __FILE__))
10
- config = Yardstick::Config.coerce(yaml)
11
- measure = Yardstick.measure(config)
12
- measure.puts
13
- coverage = Yardstick.round_percentage(measure.coverage * 100)
14
- exit(1) if coverage < config.threshold
15
- end
16
-
17
- desc('Codestyle check and linter')
18
- RuboCop::RakeTask.new('qa:code') do |task|
19
- task.fail_on_error = true
20
- task.patterns = [
21
- 'lib/**/*.rb',
22
- 'spec/**/*.rb'
23
- ]
24
- end
25
-
26
- desc('Run CI QA tasks')
27
- task(qa: ['qa:docs', 'qa:code'])
28
-
29
- RSpec::Core::RakeTask.new(spec: :qa)
30
- task(default: :spec)
data/otp-jwt.gemspec DELETED
@@ -1,36 +0,0 @@
1
- lib = File.expand_path('../lib', __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
-
4
- require 'otp/jwt/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'otp-jwt'
8
- spec.version = OTP::JWT::VERSION
9
- spec.authors = ['Stas Suscov']
10
- spec.email = ['stas@nerd.ro']
11
-
12
- spec.summary = 'Passwordless HTTP APIs'
13
- spec.description = 'OTP (email, SMS) JWT authentication for HTTP APIs.'
14
- spec.homepage = 'https://github.com/stas/otp-jwt'
15
- spec.license = 'TBD'
16
-
17
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
19
- end
20
- spec.require_paths = ['lib']
21
-
22
- spec.add_dependency 'activesupport'
23
- spec.add_dependency 'jwt', '~> 2.2.0.pre.beta.0'
24
- spec.add_dependency 'rotp', '~> 4.1'
25
-
26
- spec.add_development_dependency 'bundler'
27
- spec.add_development_dependency 'ffaker'
28
- spec.add_development_dependency 'rails'
29
- spec.add_development_dependency 'rspec-rails'
30
- spec.add_development_dependency 'rubocop-performance'
31
- spec.add_development_dependency 'rubocop-rails_config'
32
- spec.add_development_dependency 'rubocop-rspec'
33
- spec.add_development_dependency 'simplecov'
34
- spec.add_development_dependency 'sqlite3', ENV['SQLITE3_VERSION']
35
- spec.add_development_dependency 'yardstick'
36
- end