otp-jwt 0.2.1 → 0.2.6

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: c2ab72bba26dccf6e9997c1d308817a5b83bff50
4
- data.tar.gz: 2eaad486102257081e36effb3a8952497a2fe6fa
2
+ SHA256:
3
+ metadata.gz: bf8f83dacec78e2f6b12891bea71b4a45672ac967fa84fea749eb1acfe47c42c
4
+ data.tar.gz: b8c3fb4e6b0f9824a42661ea2b650135bcb2281c2c5951a24ee0d8abe527ca01
5
5
  SHA512:
6
- metadata.gz: e5799a46574afae040a12d024f3dec337d231cc2f517dfb29619d8ff5b6d3006018f009d15683d1ef089dc0744a8901bb752b63ddff74bbaa3e1207c2da3b9ef
7
- data.tar.gz: 88e4c2d697960bceb4c3617545461698fb0f474b48e3266120708038f5bc318fce98482f0ac056ce8f936e0e53e496cec4869187c1773acbf9ec68fd42115f8e
6
+ metadata.gz: 7b2806094446c6c929b5cc0023630fe3291f8736a845a92a55ce157bdcb21cabe18fba13aa80483c8408990213fc0a5ac363f23bd7f306b3eebfdc9fbc4e2b64
7
+ data.tar.gz: 0cdbaa9318e9e52095b57ca486f4b75210644e956d17bcc64267e23c739e7b0ed839f2863f480275e081a162b594e164ed8ebd60521e2271329f76f72bf498c6
@@ -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
@@ -103,6 +105,12 @@ A migration to add these two looks like this:
103
105
  $ rails g migration add_otp_to_users otp_secret:string otp_counter:integer
104
106
  ```
105
107
 
108
+ Generate `opt_secret` by running the following in rails console if you have preexisting user data:
109
+ ```
110
+ User.all.each do |u|
111
+ u.save()
112
+ end
113
+ ```
106
114
  #### Mailer support
107
115
 
108
116
  You can use the built-in mailer to deliver the OTP, just require it and
@@ -20,6 +20,7 @@ module OTP
20
20
  # @return [String] or nil if no OTP is set
21
21
  def otp
22
22
  return nil if !valid? || !persisted? || otp_secret.blank?
23
+
23
24
  otp_digits = self.class.const_get(:OTP_DIGITS)
24
25
  hotp = ROTP::HOTP.new(otp_secret, digits: otp_digits)
25
26
 
@@ -33,6 +34,8 @@ module OTP
33
34
  #
34
35
  # @return true on success, false on failure
35
36
  def verify_otp(otp)
37
+ return nil if !valid? || !persisted? || otp_secret.blank?
38
+
36
39
  hotp = ROTP::HOTP.new(otp_secret, digits: OTP_DIGITS)
37
40
  transaction do
38
41
  otp_status = hotp.verify(otp.to_s, otp_counter)
@@ -69,7 +72,6 @@ module OTP
69
72
  end
70
73
 
71
74
  private
72
-
73
75
  # Provides a default value for the OTP secret attribute
74
76
  #
75
77
  # @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.
@@ -10,19 +10,38 @@ module OTP
10
10
  # Returns a record based on the [JWT] token subject
11
11
  #
12
12
  # @param token [String] representing a [JWT] token
13
+ # @param claim_name [String] the claim name to be used, default is `sub`
13
14
  # @return [ActiveRecord::Base] model
14
- def from_jwt(token)
15
+ def from_jwt(token, claim_name = 'sub')
15
16
  OTP::JWT::Token.decode(token) do |payload|
16
- self.find_by(id: payload['sub'])
17
+ val = payload[claim_name]
18
+ pk_col = self.column_for_attribute(self.primary_key)
19
+
20
+ # Arel casts the values to the primary key type,
21
+ # which means that an UUID becomes an integer by default...
22
+ if self.connection.respond_to?(:lookup_cast_type_from_column)
23
+ pk_type = self.connection.lookup_cast_type_from_column(pk_col)
24
+ casted_val = pk_type.serialize(val)
25
+ else
26
+ casted_val = self.connection.type_cast(val, pk_col)
27
+ end
28
+
29
+ return if casted_val.to_s != val.to_s.strip
30
+
31
+ self.find_by(self.primary_key => val)
17
32
  end
18
33
  end
19
34
  end
20
35
 
21
36
  # Returns a [JWT] token for this record
22
37
  #
38
+ # @param claims [Hash] extra claims to be included
23
39
  # @return [ActiveRecord::Base] model
24
- def to_jwt
25
- OTP::JWT::Token.sign(sub: self.id)
40
+ def to_jwt(claims = nil)
41
+ OTP::JWT::Token.sign(
42
+ sub: self.send(self.class.primary_key),
43
+ **(claims || {})
44
+ )
26
45
  end
27
46
  end
28
47
  end
@@ -4,16 +4,23 @@ module OTP
4
4
  module JWT
5
5
  # Helpers to help you test the [JWT] requests.
6
6
  module TestHelpers
7
+ # Helper provides JSON content type headers
8
+ #
9
+ # @return [Hash] the relevant content type &co
10
+ def json_headers
11
+ { 'Content-Type': Mime[:json].to_s }
12
+ end
13
+
7
14
  # Helper to handle authentication requests easier
8
15
  #
9
16
  # @return [Hash] the authorization headers
10
17
  def jwt_auth_header(entity_or_subject)
11
- return {} unless entity_or_subject.present?
18
+ return json_headers if entity_or_subject.blank?
12
19
 
13
20
  token = entity_or_subject.try(:to_jwt)
14
21
  token ||= OTP::JWT::Token.sign(sub: entity_or_subject)
15
22
 
16
- { 'Authorization': "Bearer #{token}" }
23
+ { 'Authorization': "Bearer #{token}" }.merge(json_headers)
17
24
  end
18
25
 
19
26
  # Parses and returns a deserialized JSON
@@ -1,5 +1,5 @@
1
1
  module OTP
2
2
  module JWT
3
- VERSION = '0.2.1'
3
+ VERSION = '0.2.6'
4
4
  end
5
5
  end
@@ -0,0 +1,82 @@
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.timestamps
36
+ end
37
+ end
38
+
39
+ class User < ActiveRecord::Base
40
+ include GlobalID::Identification
41
+ include OTP::ActiveRecord
42
+ include OTP::JWT::ActiveRecord
43
+
44
+ def email_otp
45
+ OTP::Mailer.otp(email, otp, self).deliver_later
46
+ end
47
+ end
48
+
49
+ class ApplicationController < ActionController::Base
50
+ include OTP::JWT::ActionController
51
+
52
+ private
53
+ def current_user
54
+ @jwt_user ||= User.from_jwt(request_authorization_header)
55
+ end
56
+
57
+ def current_user!
58
+ current_user || raise('User authentication failed')
59
+ rescue
60
+ head(:unauthorized)
61
+ end
62
+ end
63
+
64
+ class UsersController < ApplicationController
65
+ before_action :current_user!
66
+
67
+ def index
68
+ render json: current_user
69
+ end
70
+ end
71
+
72
+ class TokensController < ApplicationController
73
+ def create
74
+ user = User.find_by(email: params[:email])
75
+
76
+ jwt_from_otp(user, params[:otp]) do |auth_user|
77
+ auth_user.update_column(:last_login_at, DateTime.current)
78
+
79
+ render json: { token: auth_user.to_jwt }, status: :created
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,54 @@
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
+ end
16
+
17
+ describe '#verify' do
18
+ it do
19
+ expect(described_class.verify(token).first).to include(payload)
20
+ end
21
+
22
+ it 'with a bad token' do
23
+ expect { described_class.verify(FFaker::Internet.password) }
24
+ .to raise_error(JWT::DecodeError)
25
+ end
26
+
27
+ it 'with an expired token' do
28
+ token = OTP::JWT::Token.sign(
29
+ sub: FFaker::Internet.password, exp: DateTime.now.to_i
30
+ )
31
+ expect { described_class.verify(token) }
32
+ .to raise_error(JWT::ExpiredSignature)
33
+ end
34
+ end
35
+
36
+ describe '#decode' do
37
+ let(:user) { create_user }
38
+ let(:payload) { { 'sub' => user.id } }
39
+
40
+ it do
41
+ expect(
42
+ described_class.decode(token) { |p| User.find(p['sub']) }
43
+ ).to eq(user)
44
+ end
45
+
46
+ context 'with a bad token' do
47
+ let(:token) { FFaker::Internet.password }
48
+
49
+ it do
50
+ expect(described_class.decode(token)).to eq(nil)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,70 @@
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
27
+ User.create!(
28
+ full_name: FFaker::Name.name,
29
+ email: FFaker::Internet.email,
30
+ phone_number: FFaker::PhoneNumber.phone_number
31
+ )
32
+ end
33
+ end
34
+
35
+ module Rails4RequestMethods
36
+ [:get, :post, :put, :delete].each do |method_name|
37
+ define_method(method_name) do |path, named_args|
38
+ super(
39
+ path,
40
+ named_args.delete(:params),
41
+ named_args.delete(:headers)
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ RSpec.configure do |config|
48
+ config.use_transactional_fixtures = true
49
+ config.mock_with :rspec
50
+ config.filter_run_when_matching :focus
51
+ config.disable_monkey_patching!
52
+
53
+ config.expect_with :rspec do |c|
54
+ c.syntax = :expect
55
+ end
56
+
57
+ config.include OTP::JWT::TestHelpers, type: :model
58
+ config.include OTP::JWT::FactoryHelpers, type: :model
59
+ config.include ActiveJob::TestHelper, type: :model
60
+
61
+ config.include OTP::JWT::TestHelpers, type: :request
62
+ config.include OTP::JWT::FactoryHelpers, type: :request
63
+ config.include ActiveJob::TestHelper, type: :request
64
+ config.include Dummy.routes.url_helpers, type: :request
65
+
66
+ if ::Rails::VERSION::MAJOR == 4
67
+ config.include Rails4RequestMethods, type: :request
68
+ config.include Rails4RequestMethods, type: :controller
69
+ end
70
+ 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
@@ -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,23 @@
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
+ end
17
+
18
+ context 'with bad subject' do
19
+ let(:user) { FFaker::Internet.password }
20
+
21
+ it { expect(response).to have_http_status(:unauthorized) }
22
+ end
23
+ 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.1
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stas Suscov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-18 00:00:00.000000000 Z
11
+ date: 2021-01-09 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
@@ -168,16 +182,30 @@ dependencies:
168
182
  name: sqlite3
169
183
  requirement: !ruby/object:Gem::Requirement
170
184
  requirements:
171
- - - "~>"
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: tzinfo-data
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
172
200
  - !ruby/object:Gem::Version
173
- version: 1.3.6
201
+ version: '0'
174
202
  type: :development
175
203
  prerelease: false
176
204
  version_requirements: !ruby/object:Gem::Requirement
177
205
  requirements:
178
- - - "~>"
206
+ - - ">="
179
207
  - !ruby/object:Gem::Version
180
- version: 1.3.6
208
+ version: '0'
181
209
  - !ruby/object:Gem::Dependency
182
210
  name: yardstick
183
211
  requirement: !ruby/object:Gem::Requirement
@@ -199,15 +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
208
- - Gemfile.lock
230
+ - LICENSE.txt
209
231
  - README.md
210
- - Rakefile
211
232
  - lib/otp.rb
212
233
  - lib/otp/active_record.rb
213
234
  - lib/otp/jwt.rb
@@ -219,10 +240,15 @@ files:
219
240
  - lib/otp/mailer.rb
220
241
  - lib/otp/mailer/otp.text.erb
221
242
  - lib/otp/sms_job.rb
222
- - 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
223
249
  homepage: https://github.com/stas/otp-jwt
224
250
  licenses:
225
- - TBD
251
+ - MIT
226
252
  metadata: {}
227
253
  post_install_message:
228
254
  rdoc_options: []
@@ -239,8 +265,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
265
  - !ruby/object:Gem::Version
240
266
  version: '0'
241
267
  requirements: []
242
- rubyforge_project:
243
- rubygems_version: 2.5.2.2
268
+ rubygems_version: 3.2.3
244
269
  signing_key:
245
270
  specification_version: 4
246
271
  summary: Passwordless HTTP APIs
@@ -1,30 +0,0 @@
1
- workflow "Tests" {
2
- on = "push"
3
- resolves = [
4
- "rspec-ruby2.6_rails4",
5
- "rspec-ruby2.6_rails5"
6
- ]
7
- }
8
-
9
- action "rspec-ruby2.6_rails4" {
10
- uses = "docker://ruby:2.6-alpine"
11
- env = {
12
- RAILS_VERSION = "~> 4"
13
- }
14
- args = [
15
- "sh", "-c",
16
- "apk add -U git build-base sqlite-dev && rm Gemfile.lock && bundle install && rake"
17
- ]
18
- }
19
-
20
- action "rspec-ruby2.6_rails5" {
21
- uses = "docker://ruby:2.6-alpine"
22
- needs = ["rspec-ruby2.6_rails4"]
23
- env = {
24
- RAILS_VERSION = "~> 5"
25
- }
26
- args = [
27
- "sh", "-c",
28
- "apk add -U git build-base sqlite-dev && rm Gemfile.lock && bundle install && rake"
29
- ]
30
- }
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- coverage
2
- pkg
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
@@ -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
@@ -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,6 +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']
@@ -1,197 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- otp-jwt (0.2.1)
5
- activesupport
6
- jwt (~> 2.2.0.pre.beta.0)
7
- rotp (~> 4.1)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- actioncable (5.2.3)
13
- actionpack (= 5.2.3)
14
- nio4r (~> 2.0)
15
- websocket-driver (>= 0.6.1)
16
- actionmailer (5.2.3)
17
- actionpack (= 5.2.3)
18
- actionview (= 5.2.3)
19
- activejob (= 5.2.3)
20
- mail (~> 2.5, >= 2.5.4)
21
- rails-dom-testing (~> 2.0)
22
- actionpack (5.2.3)
23
- actionview (= 5.2.3)
24
- activesupport (= 5.2.3)
25
- rack (~> 2.0)
26
- rack-test (>= 0.6.3)
27
- rails-dom-testing (~> 2.0)
28
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
29
- actionview (5.2.3)
30
- activesupport (= 5.2.3)
31
- builder (~> 3.1)
32
- erubi (~> 1.4)
33
- rails-dom-testing (~> 2.0)
34
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
35
- activejob (5.2.3)
36
- activesupport (= 5.2.3)
37
- globalid (>= 0.3.6)
38
- activemodel (5.2.3)
39
- activesupport (= 5.2.3)
40
- activerecord (5.2.3)
41
- activemodel (= 5.2.3)
42
- activesupport (= 5.2.3)
43
- arel (>= 9.0)
44
- activestorage (5.2.3)
45
- actionpack (= 5.2.3)
46
- activerecord (= 5.2.3)
47
- marcel (~> 0.3.1)
48
- activesupport (5.2.3)
49
- concurrent-ruby (~> 1.0, >= 1.0.2)
50
- i18n (>= 0.7, < 2)
51
- minitest (~> 5.1)
52
- tzinfo (~> 1.1)
53
- addressable (2.6.0)
54
- public_suffix (>= 2.0.2, < 4.0)
55
- arel (9.0.0)
56
- ast (2.4.0)
57
- builder (3.2.3)
58
- concurrent-ruby (1.1.5)
59
- crass (1.0.4)
60
- diff-lcs (1.3)
61
- docile (1.3.1)
62
- erubi (1.8.0)
63
- ffaker (2.10.0)
64
- globalid (0.4.2)
65
- activesupport (>= 4.2.0)
66
- i18n (1.6.0)
67
- concurrent-ruby (~> 1.0)
68
- jaro_winkler (1.5.2)
69
- json (2.2.0)
70
- jwt (2.2.0.pre.beta.0)
71
- loofah (2.2.3)
72
- crass (~> 1.0.2)
73
- nokogiri (>= 1.5.9)
74
- mail (2.7.1)
75
- mini_mime (>= 0.1.1)
76
- marcel (0.3.3)
77
- mimemagic (~> 0.3.2)
78
- method_source (0.9.2)
79
- mimemagic (0.3.3)
80
- mini_mime (1.0.1)
81
- mini_portile2 (2.4.0)
82
- minitest (5.11.3)
83
- nio4r (2.3.1)
84
- nokogiri (1.10.2)
85
- mini_portile2 (~> 2.4.0)
86
- parallel (1.16.2)
87
- parser (2.6.2.0)
88
- ast (~> 2.4.0)
89
- psych (3.1.0)
90
- public_suffix (3.0.3)
91
- rack (2.0.6)
92
- rack-test (1.1.0)
93
- rack (>= 1.0, < 3)
94
- rails (5.2.3)
95
- actioncable (= 5.2.3)
96
- actionmailer (= 5.2.3)
97
- actionpack (= 5.2.3)
98
- actionview (= 5.2.3)
99
- activejob (= 5.2.3)
100
- activemodel (= 5.2.3)
101
- activerecord (= 5.2.3)
102
- activestorage (= 5.2.3)
103
- activesupport (= 5.2.3)
104
- bundler (>= 1.3.0)
105
- railties (= 5.2.3)
106
- sprockets-rails (>= 2.0.0)
107
- rails-dom-testing (2.0.3)
108
- activesupport (>= 4.2.0)
109
- nokogiri (>= 1.6)
110
- rails-html-sanitizer (1.0.4)
111
- loofah (~> 2.2, >= 2.2.2)
112
- railties (5.2.3)
113
- actionpack (= 5.2.3)
114
- activesupport (= 5.2.3)
115
- method_source
116
- rake (>= 0.8.7)
117
- thor (>= 0.19.0, < 2.0)
118
- rainbow (3.0.0)
119
- rake (12.3.2)
120
- rotp (4.1.0)
121
- addressable (~> 2.5)
122
- rspec-core (3.8.0)
123
- rspec-support (~> 3.8.0)
124
- rspec-expectations (3.8.2)
125
- diff-lcs (>= 1.2.0, < 2.0)
126
- rspec-support (~> 3.8.0)
127
- rspec-mocks (3.8.0)
128
- diff-lcs (>= 1.2.0, < 2.0)
129
- rspec-support (~> 3.8.0)
130
- rspec-rails (3.8.2)
131
- actionpack (>= 3.0)
132
- activesupport (>= 3.0)
133
- railties (>= 3.0)
134
- rspec-core (~> 3.8.0)
135
- rspec-expectations (~> 3.8.0)
136
- rspec-mocks (~> 3.8.0)
137
- rspec-support (~> 3.8.0)
138
- rspec-support (3.8.0)
139
- rubocop (0.66.0)
140
- jaro_winkler (~> 1.5.1)
141
- parallel (~> 1.10)
142
- parser (>= 2.5, != 2.5.1.1)
143
- psych (>= 3.1.0)
144
- rainbow (>= 2.2.2, < 4.0)
145
- ruby-progressbar (~> 1.7)
146
- unicode-display_width (>= 1.4.0, < 1.6)
147
- rubocop-performance (1.0.0)
148
- rubocop (>= 0.58.0)
149
- rubocop-rails_config (0.4.4)
150
- railties (>= 3.0)
151
- rubocop (~> 0.58)
152
- rubocop-rspec (1.32.0)
153
- rubocop (>= 0.60.0)
154
- ruby-progressbar (1.10.0)
155
- simplecov (0.16.1)
156
- docile (~> 1.1)
157
- json (>= 1.8, < 3)
158
- simplecov-html (~> 0.10.0)
159
- simplecov-html (0.10.2)
160
- sprockets (3.7.2)
161
- concurrent-ruby (~> 1.0)
162
- rack (> 1, < 3)
163
- sprockets-rails (3.2.1)
164
- actionpack (>= 4.0)
165
- activesupport (>= 4.0)
166
- sprockets (>= 3.0.0)
167
- sqlite3 (1.3.13)
168
- thor (0.20.3)
169
- thread_safe (0.3.6)
170
- tzinfo (1.2.5)
171
- thread_safe (~> 0.1)
172
- unicode-display_width (1.5.0)
173
- websocket-driver (0.7.0)
174
- websocket-extensions (>= 0.1.0)
175
- websocket-extensions (0.1.3)
176
- yard (0.9.18)
177
- yardstick (0.9.9)
178
- yard (~> 0.8, >= 0.8.7.2)
179
-
180
- PLATFORMS
181
- ruby
182
-
183
- DEPENDENCIES
184
- bundler
185
- ffaker
186
- otp-jwt!
187
- rails
188
- rspec-rails
189
- rubocop-performance
190
- rubocop-rails_config
191
- rubocop-rspec
192
- simplecov
193
- sqlite3 (~> 1.3.6)
194
- yardstick
195
-
196
- BUNDLED WITH
197
- 1.17.3
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)
@@ -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', '~> 1.3.6'
35
- spec.add_development_dependency 'yardstick'
36
- end