otp-jwt 0.1.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 +7 -0
- data/.github/main.workflow +30 -0
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.yardstick.yml +29 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +197 -0
- data/README.md +265 -0
- data/Rakefile +30 -0
- data/lib/otp/active_record.rb +81 -0
- data/lib/otp/jwt/action_controller.rb +36 -0
- data/lib/otp/jwt/active_record.rb +29 -0
- data/lib/otp/jwt/test_helpers.rb +27 -0
- data/lib/otp/jwt/token.rb +69 -0
- data/lib/otp/jwt/version.rb +5 -0
- data/lib/otp/jwt.rb +10 -0
- data/lib/otp/mailer/otp.text.erb +6 -0
- data/lib/otp/mailer.rb +25 -0
- data/lib/otp/sms_otp_job.rb +28 -0
- data/lib/otp.rb +6 -0
- data/otp-jwt.gemspec +36 -0
- metadata +247 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 21a9ead6fc616488ac4b250bddf20522ed33d38b
|
|
4
|
+
data.tar.gz: d2f7ebaf6e27df53693ca7ce9cb733967ede96e7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d6b0faa09edf95831f1874200b28e87bf7da8d156f6f7af28940f28afe5e04b1ecb869d78278d88de2b4a6ae62ffb37dab31cea7110ae42d86f0497155e6f98e
|
|
7
|
+
data.tar.gz: 1983f079088fc8b7b3a222ec6df065483f84d3ca072f369284b643ee024271b20cc865f7aa39ddbf2d84eadb90cbc8120748b69e1624ac0c75556f04e8e4e64e
|
|
@@ -0,0 +1,30 @@
|
|
|
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
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
otp-jwt (0.1.0)
|
|
5
|
+
activesupport
|
|
6
|
+
jwt (~> 2.1)
|
|
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.1.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 (~> 5)
|
|
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/README.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# OTP JWT ⎆
|
|
2
|
+
|
|
3
|
+
One time password (email, SMS) authentication support for HTTP APIs.
|
|
4
|
+
|
|
5
|
+
> The man who wrote the book on password management has a confession to make:
|
|
6
|
+
> He blew it.
|
|
7
|
+
>
|
|
8
|
+
>— [WSJ.com](https://www.wsj.com/articles/the-man-who-wrote-those-password-rules-has-a-new-tip-n3v-r-m1-d-1502124118)
|
|
9
|
+
|
|
10
|
+
This project provides a couple of mixins to help you build
|
|
11
|
+
applications/HTTP APIs without asking your users to provide passwords.
|
|
12
|
+
|
|
13
|
+
## About
|
|
14
|
+
|
|
15
|
+
The goal of this project is to provide support for one time passwords
|
|
16
|
+
which are delivered via different channels (email, SMS), along with a
|
|
17
|
+
simple and easy to use JWT authentication.
|
|
18
|
+
|
|
19
|
+
Main goals:
|
|
20
|
+
* No _magic_ please
|
|
21
|
+
* No DSLs please
|
|
22
|
+
* Less code, less maintenance
|
|
23
|
+
* Good docs and test coverage
|
|
24
|
+
* Keep it up-to-date (or at least tell people this is no longer maintained)
|
|
25
|
+
|
|
26
|
+
The available features include:
|
|
27
|
+
* Flexible models support for
|
|
28
|
+
[counter based OTP](https://github.com/mdp/rotp#counter-based-otps)
|
|
29
|
+
* Flexible JWT token generation helpers for models and arbitrary data
|
|
30
|
+
* Pluggable authentication based on the OTP and JWT
|
|
31
|
+
* Pluggable OTP mailer
|
|
32
|
+
* Pluggable OTP SMS background processing job
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
This little project wouldn't be possible without the previous work on
|
|
36
|
+
[ROTP](https://github.com/mdp/rotp)
|
|
37
|
+
and [JWT](https://github.com/jwt/ruby-jwt/).
|
|
38
|
+
|
|
39
|
+
Thanks to everyone who worked on these amazing projects!
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Add this line to your application's Gemfile:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
gem 'otp-jwt'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
And then execute:
|
|
50
|
+
|
|
51
|
+
$ bundle
|
|
52
|
+
|
|
53
|
+
Or install it yourself as:
|
|
54
|
+
|
|
55
|
+
$ gem install otp-jwt
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
* [OTP for Active Record models](#otp-for-active-record-models)
|
|
60
|
+
* [Mailer support](#mailer-support)
|
|
61
|
+
* [SMS delivery support](#sms-delivery-support)
|
|
62
|
+
* [JWT for Active Record models](#jwt-for-active-record-models)
|
|
63
|
+
* [JWT authorization](#jwt-authorization)
|
|
64
|
+
* [JWT authentication](#jwt-authentication)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
To start using it with Rails, add this to an initializer and configure your
|
|
69
|
+
keys:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
# config/initializers/otp-jwt.rb
|
|
73
|
+
require 'otp'
|
|
74
|
+
# To load the JWT related support.
|
|
75
|
+
require 'otp/jwt'
|
|
76
|
+
|
|
77
|
+
OTP::JWT::Token.jwt_signature_key = ENV['YOUR-SIGN-KEY']
|
|
78
|
+
```
|
|
79
|
+
### OTP for Active Record models
|
|
80
|
+
|
|
81
|
+
To add support for OTP to your models, use the `OTP::ActiveRecord` concern:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class User < ActiveRecord::Base
|
|
85
|
+
include OTP::ActiveRecord
|
|
86
|
+
|
|
87
|
+
...
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This will provide two new methods which you can use to generate and verify
|
|
92
|
+
one time passwords:
|
|
93
|
+
* `User#otp`
|
|
94
|
+
* `User#verify_otp`
|
|
95
|
+
|
|
96
|
+
This concern expects two attributes to be provided by the model, the:
|
|
97
|
+
* `otp_secret`: of type string, used to store the OTP signature key
|
|
98
|
+
* `otp_counter`: of type integer, used to store the OTP counter
|
|
99
|
+
|
|
100
|
+
A migration to add these two looks like this:
|
|
101
|
+
```
|
|
102
|
+
$ rails g migration add_otp_to_users otp_secret:string otp_counter:integer
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Mailer support
|
|
106
|
+
|
|
107
|
+
You can use the built-in mailer to deliver the OTP, just require it and
|
|
108
|
+
overwrite the helper method:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
require 'otp/mailer'
|
|
112
|
+
|
|
113
|
+
class User < ActiveRecord::Base
|
|
114
|
+
include OTP::ActiveRecord
|
|
115
|
+
|
|
116
|
+
def email_otp
|
|
117
|
+
OTP::Mailer.otp(email, otp, self).deliver_later
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### SMS delivery support
|
|
123
|
+
|
|
124
|
+
You can use the built-in job to deliver the OTP via SMS, just require it and
|
|
125
|
+
overwrite the helper method:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
require 'otp/sms_otp_job'
|
|
129
|
+
|
|
130
|
+
class User < ActiveRecord::Base
|
|
131
|
+
include OTP::ActiveRecord
|
|
132
|
+
|
|
133
|
+
def sms_otp
|
|
134
|
+
OTP::SMSOTPJob.perform_later(phone_number, otp) if phone_number.present?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
You will have to provide your model with the phone number attribute if you
|
|
140
|
+
want to deliver the OTPs via SMS.
|
|
141
|
+
|
|
142
|
+
This job requires `aws-sdk-sns` gem to work. You will have to add it manually
|
|
143
|
+
and configure to use the correct keys. The SNS region is fetched from the
|
|
144
|
+
environment variable `AWS_SMS_REGION`.
|
|
145
|
+
|
|
146
|
+
### JWT for Active Record models
|
|
147
|
+
|
|
148
|
+
To add support for JWT to your models, use the `OTP::JWT::ActiveRecord` concern:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
class User < ActiveRecord::Base
|
|
152
|
+
include OTP::JWT::ActiveRecord
|
|
153
|
+
|
|
154
|
+
...
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
This will provide two new methods which you can use to generate and verify JWT
|
|
159
|
+
tokens:
|
|
160
|
+
* `User#from_jwt`
|
|
161
|
+
* `User#to_jwt`
|
|
162
|
+
|
|
163
|
+
### JWT authorization
|
|
164
|
+
|
|
165
|
+
To add support for JWT to your controllers,
|
|
166
|
+
use the `OTP::JWT::ActionController` concern:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
class ApplicationController < ActionController::Base
|
|
170
|
+
include OTP::JWT::ActionController
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def current_user
|
|
175
|
+
@jwt_user ||= User.from_jwt(request_authorization_header)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def current_user!
|
|
179
|
+
current_user || raise('User authentication failed')
|
|
180
|
+
rescue
|
|
181
|
+
head(:unauthorized)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The example from above includes helpers you can use interact with the
|
|
187
|
+
currently authenticated user or just use as part of `before_action` callback.
|
|
188
|
+
|
|
189
|
+
The `request_authorization_header` method is also provided by the concern and
|
|
190
|
+
allows you to customize from where the token is received. A query parameter
|
|
191
|
+
based alternative would look like this:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
class ApplicationController < ActionController::Base
|
|
195
|
+
include OTP::JWT::ActionController
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def current_user
|
|
200
|
+
@jwt_user ||= User.from_jwt(params[:token])
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
...
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### JWT authentication
|
|
208
|
+
|
|
209
|
+
The `OTP::JWT::ActionController` concern provides support for handling the
|
|
210
|
+
authentication requests and token generation by using the `jwt_from_otp` method.
|
|
211
|
+
|
|
212
|
+
Here's an example of a tokens controller:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class TokensController < ApplicationController
|
|
216
|
+
def create
|
|
217
|
+
user = User.find_by(email: params[:email])
|
|
218
|
+
|
|
219
|
+
jwt_from_otp(user, params[:otp]) do |auth_user|
|
|
220
|
+
# Let's update the last login date before we send the token...
|
|
221
|
+
# auth_user.update_column(:last_login_at, DateTime.current)
|
|
222
|
+
|
|
223
|
+
render json: { token: auth_user.to_jwt }, status: :created
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The `jwt_from_otp` does a couple of things here:
|
|
230
|
+
* It will try to authenticate the user you found by email and respond with
|
|
231
|
+
a valid JWT token
|
|
232
|
+
* It will try to schedule an email or SMS delivery of the OTP and it will
|
|
233
|
+
respond with the 400 HTTP status
|
|
234
|
+
* It will respond with the 403 HTTP status if there's no user
|
|
235
|
+
or the OTP is wrong
|
|
236
|
+
|
|
237
|
+
The OTP delivery is handled by the `User#deliver_otp` method
|
|
238
|
+
and can be customized. By default it will call the `sms_otp` method and
|
|
239
|
+
if nothing is returned, it will proceed with the `email_otp` method.
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
After checking out the repo, run `bundle` to install dependencies.
|
|
244
|
+
|
|
245
|
+
Then, run `rake` to run the tests.
|
|
246
|
+
|
|
247
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
248
|
+
|
|
249
|
+
To release a new version, update the version number in `version.rb`, and then
|
|
250
|
+
run `bundle exec rake release`, which will create a git tag for the version,
|
|
251
|
+
push git commits and tags, and push the `.gem` file to
|
|
252
|
+
[rubygems.org](https://rubygems.org).
|
|
253
|
+
|
|
254
|
+
## Contributing
|
|
255
|
+
|
|
256
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
257
|
+
https://github.com/stas/otp-jwt
|
|
258
|
+
|
|
259
|
+
This project is intended to be a safe, welcoming space for collaboration, and
|
|
260
|
+
contributors are expected to adhere to the
|
|
261
|
+
[Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
TBD
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
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)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'rotp'
|
|
2
|
+
require 'active_support/concern'
|
|
3
|
+
require 'active_support/configurable'
|
|
4
|
+
|
|
5
|
+
module OTP
|
|
6
|
+
# [ActiveRecord] concern.
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
include ActiveSupport::Configurable
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
# Length of the generated OTP, defaults to 4.
|
|
12
|
+
OTP_DIGITS = 4
|
|
13
|
+
|
|
14
|
+
included do
|
|
15
|
+
after_initialize :setup_otp
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Generates the OTP
|
|
19
|
+
#
|
|
20
|
+
# @return [String] or nil if no OTP is set
|
|
21
|
+
def otp
|
|
22
|
+
return nil unless valid? && persisted?
|
|
23
|
+
otp_digits = self.class.const_get(:OTP_DIGITS)
|
|
24
|
+
hotp = ROTP::HOTP.new(otp_secret, digits: otp_digits)
|
|
25
|
+
|
|
26
|
+
transaction do
|
|
27
|
+
increment!(:otp_counter)
|
|
28
|
+
hotp.at(otp_counter)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Verifies the OTP
|
|
33
|
+
#
|
|
34
|
+
# @return true on success, false on failure
|
|
35
|
+
def verify_otp(otp)
|
|
36
|
+
hotp = ROTP::HOTP.new(otp_secret, digits: OTP_DIGITS)
|
|
37
|
+
transaction do
|
|
38
|
+
otp_status = hotp.verify(otp.to_s, otp_counter)
|
|
39
|
+
increment!(:otp_counter)
|
|
40
|
+
otp_status
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Helper to send the OTP using the SMS job
|
|
45
|
+
#
|
|
46
|
+
# Does nothing. Implement your own handler.
|
|
47
|
+
#
|
|
48
|
+
# @return [OTP::API::SMSOTPJob] instance of the job
|
|
49
|
+
def sms_otp
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Helper to email the OTP using a job
|
|
53
|
+
#
|
|
54
|
+
# Does nothing. Implement your own handler.
|
|
55
|
+
#
|
|
56
|
+
# @return [OTP::API::Mailer] instance of the job
|
|
57
|
+
def email_otp
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Helper to deliver the OTP
|
|
61
|
+
#
|
|
62
|
+
# Will use the SMS job if the phone number is available.
|
|
63
|
+
# Will default to the email delivery.
|
|
64
|
+
#
|
|
65
|
+
# @return [ActiveJob::Base] instance of the job
|
|
66
|
+
def deliver_otp
|
|
67
|
+
return unless persisted?
|
|
68
|
+
sms_otp || email_otp || raise(NotImplementedError, self)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# Provides a default value for the OTP secret attribute
|
|
74
|
+
#
|
|
75
|
+
# @return [String]
|
|
76
|
+
def setup_otp
|
|
77
|
+
self.otp_secret ||= ROTP::Base32.random_base32
|
|
78
|
+
self.otp_counter ||= 0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module OTP
|
|
2
|
+
module JWT
|
|
3
|
+
# [ActionController] concern.
|
|
4
|
+
module ActionController
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
# Authenticates a model and responds with a [JWT] token
|
|
8
|
+
#
|
|
9
|
+
# @return [String] with authentication token and country shop ID.
|
|
10
|
+
def jwt_from_otp(model, otp)
|
|
11
|
+
# Send the OTP if the model is trying to authenticate.
|
|
12
|
+
if model.present? && otp.blank?
|
|
13
|
+
job = model.deliver_otp
|
|
14
|
+
return render(json: { job_id: job.job_id }, status: :bad_request)
|
|
15
|
+
elsif model.present? && otp.present? && !model.verify_otp(otp)
|
|
16
|
+
return head(:forbidden)
|
|
17
|
+
elsif model.blank?
|
|
18
|
+
return head(:forbidden)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return yield(model) if block_given?
|
|
22
|
+
|
|
23
|
+
render json: { token: model.to_jwt }, status: :created
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Extracts a token from the authorization header
|
|
27
|
+
#
|
|
28
|
+
# @return [String] the token present in the header or nothing.
|
|
29
|
+
def request_authorization_header
|
|
30
|
+
return if request.headers['Authorization'].blank?
|
|
31
|
+
|
|
32
|
+
request.headers['Authorization'].split.last
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'active_support/concern'
|
|
2
|
+
|
|
3
|
+
module OTP
|
|
4
|
+
module JWT
|
|
5
|
+
# [ActiveRecord] concern.
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
class_methods do
|
|
10
|
+
# Returns a record based on the [JWT] token subject
|
|
11
|
+
#
|
|
12
|
+
# @param token [String] representing a [JWT] token
|
|
13
|
+
# @return [ActiveRecord::Base] model
|
|
14
|
+
def from_jwt(token)
|
|
15
|
+
OTP::JWT::Token.decode(token) do |payload|
|
|
16
|
+
self.find_by(id: payload['sub'])
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Returns a [JWT] token for this record
|
|
22
|
+
#
|
|
23
|
+
# @return [ActiveRecord::Base] model
|
|
24
|
+
def to_jwt
|
|
25
|
+
OTP::JWT::Token.sign(sub: self.id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module OTP
|
|
4
|
+
module JWT
|
|
5
|
+
# Helpers to help you test the [JWT] requests.
|
|
6
|
+
module TestHelpers
|
|
7
|
+
# Helper to handle authentication requests easier
|
|
8
|
+
#
|
|
9
|
+
# @return [Hash] the authorization headers
|
|
10
|
+
def jwt_auth_header(entity_or_subject)
|
|
11
|
+
return {} unless entity_or_subject.present?
|
|
12
|
+
|
|
13
|
+
token = entity_or_subject.try(:to_jwt)
|
|
14
|
+
token ||= OTP::JWT::Token.sign(sub: entity_or_subject)
|
|
15
|
+
|
|
16
|
+
{ 'Authorization': "Bearer #{token}" }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Parses and returns a deserialized JSON
|
|
20
|
+
#
|
|
21
|
+
# @return [Hash]
|
|
22
|
+
def response_json
|
|
23
|
+
JSON.parse(response.body)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'jwt'
|
|
2
|
+
require 'active_support/configurable'
|
|
3
|
+
|
|
4
|
+
module OTP
|
|
5
|
+
module JWT
|
|
6
|
+
# A configurable set of token helpers to sign/verify an entity JWT token.
|
|
7
|
+
module Token
|
|
8
|
+
include ActiveSupport::Configurable
|
|
9
|
+
|
|
10
|
+
# Resolves possible JWT exception classes
|
|
11
|
+
#
|
|
12
|
+
# Can be removed once #255 is merged.
|
|
13
|
+
# See: https://github.com/jwt/ruby-jwt/pull/255
|
|
14
|
+
JWT_EXCEPTIONS = ::JWT.constants.map do |cname|
|
|
15
|
+
klass = ::JWT.const_get(cname)
|
|
16
|
+
klass if klass.is_a?(Class) && klass <= StandardError
|
|
17
|
+
end.compact
|
|
18
|
+
|
|
19
|
+
# The signature key used to sign the tokens.
|
|
20
|
+
config_accessor :jwt_signature_key, instance_accessor: false
|
|
21
|
+
# The signature key algorithm, defaults to HS256.
|
|
22
|
+
config_accessor(:jwt_algorithm, instance_accessor: false) { 'HS256' }
|
|
23
|
+
# The lifetime of the token, defaults to 1 day.
|
|
24
|
+
config_accessor(:jwt_lifetime, instance_accessor: false) { 60 * 60 * 24 }
|
|
25
|
+
|
|
26
|
+
# Generates a token based on a payload and optional overwritable claims
|
|
27
|
+
#
|
|
28
|
+
# @param payload [Hash], data to be encoded into the token.
|
|
29
|
+
# @param claims [Hash], extra claims to be encoded into the token.
|
|
30
|
+
#
|
|
31
|
+
# @return [String], a JWT token
|
|
32
|
+
def self.sign(payload, claims = {})
|
|
33
|
+
payload = payload.merge(claims)
|
|
34
|
+
claims[:exp] ||= self.jwt_lifetime if self.jwt_lifetime.present?
|
|
35
|
+
|
|
36
|
+
::JWT.encode(payload, self.jwt_signature_key, self.jwt_algorithm)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Verifies and returns decoded token data upon success
|
|
40
|
+
#
|
|
41
|
+
# @param token [String], token to be decoded.
|
|
42
|
+
# @param options [Hash], extra options to be used during verification.
|
|
43
|
+
#
|
|
44
|
+
# @return [Hash], JWT token payload
|
|
45
|
+
def self.verify(token, options = {})
|
|
46
|
+
lifetime = self.jwt_lifetime
|
|
47
|
+
opts = {}.merge(options)
|
|
48
|
+
opts[:verify_expiration] ||= lifetime if lifetime.present?
|
|
49
|
+
|
|
50
|
+
::JWT.decode(token.to_s, self.jwt_signature_key, true, opts)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Decodes a valid token into [Hash]
|
|
54
|
+
#
|
|
55
|
+
# Requires a block, yields JWT data. Will catch any JWT exception.
|
|
56
|
+
#
|
|
57
|
+
# @param token [String], token to be decoded.
|
|
58
|
+
# @param options [Hash], extra options to be used during verification.
|
|
59
|
+
# @return [Hash] upon success
|
|
60
|
+
def self.decode(token, options = nil)
|
|
61
|
+
return unless block_given?
|
|
62
|
+
verified, _ = self.verify(token, options || {})
|
|
63
|
+
|
|
64
|
+
yield verified
|
|
65
|
+
rescue *JWT_EXCEPTIONS
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/otp/jwt.rb
ADDED
data/lib/otp/mailer.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require 'action_mailer/railtie'
|
|
2
|
+
require_relative '../otp'
|
|
3
|
+
|
|
4
|
+
module OTP
|
|
5
|
+
# OTP mailer.
|
|
6
|
+
class Mailer < ActionMailer::Base
|
|
7
|
+
append_view_path(OTP::PATH)
|
|
8
|
+
|
|
9
|
+
default subject: 'Your magic password 🗝️'
|
|
10
|
+
|
|
11
|
+
# Sends an email containing the OTP
|
|
12
|
+
#
|
|
13
|
+
# @param email [String] the email address to send to
|
|
14
|
+
# @param otp_code [String] the OTP code to include
|
|
15
|
+
# @param model [ActiveRecord::Base] model to expose
|
|
16
|
+
# @param mail_opts [Hash] arbitrary options to pass to `mail()` method
|
|
17
|
+
# @return [Mail] instance
|
|
18
|
+
def otp(email, otp_code, model, mail_opts = {})
|
|
19
|
+
@model = model
|
|
20
|
+
@otp_code = otp_code
|
|
21
|
+
|
|
22
|
+
mail(to: email, **mail_opts)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'active_job/railtie'
|
|
2
|
+
begin
|
|
3
|
+
require 'aws-sdk-sns'
|
|
4
|
+
rescue LoadError => err
|
|
5
|
+
(Rails.logger || Logger.new(STDOUT)).error(err)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module OTP
|
|
9
|
+
# Uses the AWS SNS API to send the OTP SMS message.
|
|
10
|
+
class SMSOTPJob < ActiveJob::Base
|
|
11
|
+
# A generic template for the message body.
|
|
12
|
+
TEMPLATE = '%{otp} is your magic password 🗝️'
|
|
13
|
+
# Indicates if the messaging is disabled. Handy for testing purposes.
|
|
14
|
+
ENABLED = true
|
|
15
|
+
|
|
16
|
+
# Sends the SMS message with the OTP code
|
|
17
|
+
#
|
|
18
|
+
# @return nil
|
|
19
|
+
def perform(phone_number, otp_code, template = TEMPLATE)
|
|
20
|
+
message = template % { otp: otp_code }
|
|
21
|
+
|
|
22
|
+
Aws::SNS::Client.new(region: ENV['AWS_SMS_REGION']).publish(
|
|
23
|
+
message: message,
|
|
24
|
+
phone_number: phone_number
|
|
25
|
+
) if ENABLED
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/otp.rb
ADDED
data/otp-jwt.gemspec
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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.1'
|
|
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
|
metadata
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: otp-jwt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Stas Suscov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2019-03-31 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: jwt
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.1'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.1'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rotp
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '4.1'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '4.1'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: bundler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: ffaker
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rails
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec-rails
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop-performance
|
|
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'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop-rails_config
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: rubocop-rspec
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - ">="
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '0'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - ">="
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: simplecov
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: sqlite3
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: 1.3.6
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: 1.3.6
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: yardstick
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
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
|
+
description: OTP (email, SMS) JWT authentication for HTTP APIs.
|
|
196
|
+
email:
|
|
197
|
+
- stas@nerd.ro
|
|
198
|
+
executables: []
|
|
199
|
+
extensions: []
|
|
200
|
+
extra_rdoc_files: []
|
|
201
|
+
files:
|
|
202
|
+
- ".github/main.workflow"
|
|
203
|
+
- ".gitignore"
|
|
204
|
+
- ".rspec"
|
|
205
|
+
- ".rubocop.yml"
|
|
206
|
+
- ".yardstick.yml"
|
|
207
|
+
- Gemfile
|
|
208
|
+
- Gemfile.lock
|
|
209
|
+
- README.md
|
|
210
|
+
- Rakefile
|
|
211
|
+
- lib/otp.rb
|
|
212
|
+
- lib/otp/active_record.rb
|
|
213
|
+
- lib/otp/jwt.rb
|
|
214
|
+
- lib/otp/jwt/action_controller.rb
|
|
215
|
+
- lib/otp/jwt/active_record.rb
|
|
216
|
+
- lib/otp/jwt/test_helpers.rb
|
|
217
|
+
- lib/otp/jwt/token.rb
|
|
218
|
+
- lib/otp/jwt/version.rb
|
|
219
|
+
- lib/otp/mailer.rb
|
|
220
|
+
- lib/otp/mailer/otp.text.erb
|
|
221
|
+
- lib/otp/sms_otp_job.rb
|
|
222
|
+
- otp-jwt.gemspec
|
|
223
|
+
homepage: https://github.com/stas/otp-jwt
|
|
224
|
+
licenses:
|
|
225
|
+
- TBD
|
|
226
|
+
metadata: {}
|
|
227
|
+
post_install_message:
|
|
228
|
+
rdoc_options: []
|
|
229
|
+
require_paths:
|
|
230
|
+
- lib
|
|
231
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
232
|
+
requirements:
|
|
233
|
+
- - ">="
|
|
234
|
+
- !ruby/object:Gem::Version
|
|
235
|
+
version: '0'
|
|
236
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
237
|
+
requirements:
|
|
238
|
+
- - ">="
|
|
239
|
+
- !ruby/object:Gem::Version
|
|
240
|
+
version: '0'
|
|
241
|
+
requirements: []
|
|
242
|
+
rubyforge_project:
|
|
243
|
+
rubygems_version: 2.5.2.2
|
|
244
|
+
signing_key:
|
|
245
|
+
specification_version: 4
|
|
246
|
+
summary: Passwordless HTTP APIs
|
|
247
|
+
test_files: []
|