2fa 0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/2fa.gemspec +16 -0
- data/app/controllers/tfa/tfas_controller.rb +8 -0
- data/app/helpers/tfa/tfa_helper.rb +68 -0
- data/app/models/TFA/tfa.rb +4 -0
- data/app/views/tfa/tfas/_show.html.erb +17 -0
- data/app/views/tfa/tfas/show.html.erb +11 -0
- data/config/routes.rb +3 -0
- data/db/migrate/9999999999_create_tfas.rb +12 -0
- data/lib/2fa.rb +1 -0
- data/lib/TFA/engine.rb +19 -0
- data/lib/TFA/twilio.rb +20 -0
- data/lib/TFA/version.rb +14 -0
- data/lib/TFA.rb +9 -0
- data/readme.md +82 -0
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 581baafb237e2897facab0f9d6a21a5ce8ed2be2f0f32fd85d2b16de58d32e1c
|
4
|
+
data.tar.gz: e9fa994ebf0cf2d2b6cd9b97859e28e90147e4fc3e62d47650855e2b27437dba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2182301e7efb89cb639a00a4e8038854dc5d59d30996be2bb18c18f9279f3da1793da6a86fca6dd81bead89be94b69a71f09d1396479ca6abff26be6ff0aeeaf
|
7
|
+
data.tar.gz: cb81b21b6a96dd7759e03779c1d067d3c0e28b31fe35e8732e99f7fbe5931699057954d085168573f388c89ba1bea950466ba03801806b7a5f3aeace65fcc8ad
|
data/2fa.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'lib/TFA/version.rb'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = '2fa'
|
5
|
+
s.version = TFA.version
|
6
|
+
s.summary = 'Two factor authentication in rails apps made easy'
|
7
|
+
s.description = 'An easy way to add sms-based two factor authentication to rails apps with Twilio'
|
8
|
+
s.authors = ["Matthias Lee"]
|
9
|
+
s.email = 'matthias@matthiasclee.com'
|
10
|
+
s.files = Dir.glob("**/*")
|
11
|
+
s.files.delete("2fa-#{TFA.version}.gem")
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.add_runtime_dependency 'twilio-ruby', '>= 5.66.0'
|
14
|
+
s.add_runtime_dependency 'rails', '>= 6.1.0'
|
15
|
+
s.homepage = 'https://github.com/Matthiasclee/2fa'
|
16
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module TFA
|
2
|
+
module TfaHelper
|
3
|
+
def require_tfa(
|
4
|
+
url:,
|
5
|
+
method: :post,
|
6
|
+
http_params: {},
|
7
|
+
phone_number:,
|
8
|
+
message: "Your two factor authentication code is:\n\n{code}",
|
9
|
+
length: 6
|
10
|
+
)
|
11
|
+
@tfa = Tfa.new
|
12
|
+
@tfa.phone = phone_number
|
13
|
+
@tfa.used = false
|
14
|
+
|
15
|
+
http_params_ = ""
|
16
|
+
http_params.each do |p|
|
17
|
+
http_params_ << "#{p[0]}:#{p[1].gsub(':', "\0001").gsub(',', "\0002").gsub('#', "\0003")},"
|
18
|
+
end
|
19
|
+
|
20
|
+
@tfa.after = "#{method}###{url}###{http_params_}"
|
21
|
+
@tfa.code = rand((10**(length-1))..("9"*length).to_i)
|
22
|
+
@tfa.save
|
23
|
+
|
24
|
+
Twilio.send_msg(
|
25
|
+
message.gsub("{code}", @tfa.code.to_s),
|
26
|
+
to: phone_number
|
27
|
+
)
|
28
|
+
|
29
|
+
controller.redirect_to Engine.routes.url_helpers.tfa_verify_path(@tfa)
|
30
|
+
end
|
31
|
+
|
32
|
+
def if_tfa(&block)
|
33
|
+
if params[:tfa_id] && Tfa.find_by(id: params[:tfa_id]).code.to_s == params[:code].to_s && !Tfa.find_by(id: params[:tfa_id]).used
|
34
|
+
@tfa = Tfa.find_by(id: params[:tfa_id])
|
35
|
+
@tfa.used = true
|
36
|
+
@tfa.save
|
37
|
+
|
38
|
+
block.call(@tfa)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def no_tfa(&block)
|
43
|
+
if !params[:tfa_id]
|
44
|
+
block.call
|
45
|
+
elsif Tfa.find_by(id: params[:tfa_id]).code.to_s != params[:code].to_s
|
46
|
+
block.call
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def tfa_friendly_params(p)
|
51
|
+
p.permit!
|
52
|
+
p = p.to_h
|
53
|
+
out = {}
|
54
|
+
|
55
|
+
p.each do |param|
|
56
|
+
if param[1].class != ActiveSupport::HashWithIndifferentAccess
|
57
|
+
out[param[0]] = param[1]
|
58
|
+
else
|
59
|
+
param[1].each do |p1|
|
60
|
+
out["#{param[0]}[#{p1[0]}]"] = p1[1]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
return out
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h1>Please enter the code we sent to <%= @tfa_phone %></h1>
|
2
|
+
|
3
|
+
<%= form_with(url: @url, method: @method) do |f| %>
|
4
|
+
<%= f.hidden_field :tfa_id, value: @tfa.id %>
|
5
|
+
<% @http_params.each do |p| %>
|
6
|
+
<%= f.hidden_field p[0].to_sym, value: p[1].to_s.gsub("\0001", ':').gsub("\0002", ',').gsub("\0003", '#') %>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<div class='field'>
|
10
|
+
<%= f.number_field :code %>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class='actions'>
|
14
|
+
<%= f.submit 'Go' %>
|
15
|
+
</div>
|
16
|
+
<% end %>
|
17
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% if !@tfa.used
|
2
|
+
after = @tfa.after.split("##")
|
3
|
+
@method = after[0].to_sym
|
4
|
+
@url = after[1]
|
5
|
+
@http_params = {}
|
6
|
+
@http_params = after[2].split(",").map{|i|i.split(":").length==2 ? i.split(":") : [i.reverse.sub(":","").reverse, ""]}.to_h if after[2]
|
7
|
+
end %>
|
8
|
+
<% tfa_phone_arr = @tfa.phone.split("")
|
9
|
+
@tfa_phone = "#{tfa_phone_arr[0..1].join} (#{tfa_phone_arr[2..4].join}) *** **#{tfa_phone_arr[10..11].join}" %>
|
10
|
+
|
11
|
+
<%= render partial: 'tfa/tfas/show' %>
|
data/config/routes.rb
ADDED
data/lib/2fa.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'TFA.rb'
|
data/lib/TFA/engine.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module TFA
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace TFA
|
4
|
+
|
5
|
+
initializer 'local_helper.action_controller' do
|
6
|
+
ActiveSupport.on_load :action_controller do
|
7
|
+
helper TFA::Engine.helpers
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer :append_migrations do |app|
|
12
|
+
unless app.root.to_s.match root.to_s
|
13
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
14
|
+
app.config.paths["db/migrate"] << expanded_path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/TFA/twilio.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module TFA
|
2
|
+
module Twilio
|
3
|
+
@@acc_sid = ENV["TWILIO_ACCOUNT_SID"]
|
4
|
+
@@auth_token = ENV["TWILIO_AUTH_TOKEN"]
|
5
|
+
@@sending_phone = ENV["TWILIO_SENDING_PHONE"]
|
6
|
+
@@client = ::Twilio::REST::Client.new(@@acc_sid, @@auth_token)
|
7
|
+
|
8
|
+
mattr_reader :acc_sid
|
9
|
+
mattr_reader :auth_token
|
10
|
+
mattr_accessor :sending_phone
|
11
|
+
|
12
|
+
def self.send_msg(message, to:)
|
13
|
+
@@client.messages.create(
|
14
|
+
body: message,
|
15
|
+
from: @@sending_phone,
|
16
|
+
to: to
|
17
|
+
).sid
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/TFA/version.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module TFA
|
2
|
+
@@version = {
|
3
|
+
major: 0,
|
4
|
+
minor: 0,
|
5
|
+
patch: 1,
|
6
|
+
extra: [
|
7
|
+
]
|
8
|
+
}
|
9
|
+
|
10
|
+
def self.version
|
11
|
+
"#{@@version[:major]}.#{@@version[:minor]}.#{@@version[:patch]}#{"." if @@version[:extra].length > 0}#{@@version[:extra].join(".")}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
data/lib/TFA.rb
ADDED
data/readme.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# 2fa
|
2
|
+
|
3
|
+
### 2fa is a gem that uses the twilio-ruby library to do two factor authentication in rails apps.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
* Add `gem '2fa'` to your `Gemfile`
|
7
|
+
* `bundle install`
|
8
|
+
* Add `mount TFA::Engine => '/tfa', as: :tfa` to your `config/routes.rb`
|
9
|
+
* `rails db:migrate`
|
10
|
+
* Set the env vars, `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_SENDING_PHONE` to your twilio account sid, twilio auth token, and the phone number to send from in `+155566667777` format respectively. (You do need a twilio account for this)
|
11
|
+
|
12
|
+
## Usage
|
13
|
+
|
14
|
+
### require\_tfa()
|
15
|
+
To require 2FA anywhere in your app, simply call the `helpers.require_tfa` method.
|
16
|
+
This method takes in a phone number, the http params to be used after, the url to redirect to after, and the http method to use after, the length of the code, and the message to send.
|
17
|
+
Only the phone number, the http params, and the url are required. The rest default to post, 6, and "Your two factor authentication code is:\n\n{code}"
|
18
|
+
|
19
|
+
You can use the method like this:
|
20
|
+
```rb
|
21
|
+
helpers.require_tfa(phone_number: '+15556667777', http_params: helpers.tfa_friendly_params(params), url: request.path)
|
22
|
+
```
|
23
|
+
|
24
|
+
You can see that we givve a phone number, a url, and http params. We are though passing the params through `helpers.tfa_friendly_params`. That method just converts the params into a hash and some other stuff.
|
25
|
+
|
26
|
+
### if\_tfa and no\_tfa
|
27
|
+
You can check in your controller if you are authenticated with tfa by putting the code to only run with tfa in a `if_tfa` block and the other stuff in a `no_tfa` block.
|
28
|
+
An example of a full controller method with tfa auth:
|
29
|
+
```rb
|
30
|
+
def create
|
31
|
+
helpers.no_tfa do
|
32
|
+
helpers.require_tfa(phone_number: '+15556667777', http_params: helpers.tfa_friendly_params(params), url: request.path)
|
33
|
+
end
|
34
|
+
|
35
|
+
helpers.if_tfa do
|
36
|
+
@example = example.new(example_params)
|
37
|
+
|
38
|
+
respond_to do |format|
|
39
|
+
if @example.save
|
40
|
+
format.html { redirect_to example_url(@example), notice: "example was successfully created." }
|
41
|
+
format.json { render :show, status: :created, location: @example }
|
42
|
+
else
|
43
|
+
format.html { render :new, status: :unprocessable_entity }
|
44
|
+
format.json { render json: @example.errors, status: :unprocessable_entity }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
This is just a default scaffolded controller but with tfa auth.
|
51
|
+
|
52
|
+
This isn't perfect as in theory someone could pass a different unused tfa by inspecting the form, so to prevent this, you can take in a `|tfa|` argument from the helper to verify that the tfa has the correct phone number.
|
53
|
+
```rb
|
54
|
+
helpers.if_tfa do |tfa|
|
55
|
+
if tfa.phone == @user.phone
|
56
|
+
#...your code here
|
57
|
+
else
|
58
|
+
head 401
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
### Custom tfa check page
|
64
|
+
To make a custom tfa check page, make the file `app/views/tfa/tfas/_show.html.erb`.
|
65
|
+
In this file you have the instance variable, `@tfa` and a formatted phone number (`@tfa_phone`), and some more.
|
66
|
+
You need to have the default form stuff in there:
|
67
|
+
```rb
|
68
|
+
<%= form_with(url: @url, method: @method) do |f| %>
|
69
|
+
<%= f.hidden_field :tfa_id, value: @tfa.id %>
|
70
|
+
<% @http_params.each do |p| %>
|
71
|
+
<%= f.hidden_field p[0].to_sym, value: p[1].to_s.gsub("\0001", ':').gsub("\0002", ',').gsub("\0003", '#') %>
|
72
|
+
<% end %>
|
73
|
+
|
74
|
+
<div class='field'>
|
75
|
+
<%= f.number_field :code %>
|
76
|
+
</div>
|
77
|
+
|
78
|
+
<div class='actions'>
|
79
|
+
<%= f.submit 'Go' %>
|
80
|
+
</div>
|
81
|
+
<% end %>
|
82
|
+
```
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: 2fa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-04-
|
11
|
+
date: 2022-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: twilio-ruby
|
@@ -30,21 +30,36 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 6.1.0
|
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:
|
41
|
-
description:
|
40
|
+
version: 6.1.0
|
41
|
+
description: An easy way to add sms-based two factor authentication to rails apps
|
42
|
+
with Twilio
|
42
43
|
email: matthias@matthiasclee.com
|
43
44
|
executables: []
|
44
45
|
extensions: []
|
45
46
|
extra_rdoc_files: []
|
46
|
-
files:
|
47
|
-
|
47
|
+
files:
|
48
|
+
- 2fa.gemspec
|
49
|
+
- app/controllers/tfa/tfas_controller.rb
|
50
|
+
- app/helpers/tfa/tfa_helper.rb
|
51
|
+
- app/models/TFA/tfa.rb
|
52
|
+
- app/views/tfa/tfas/_show.html.erb
|
53
|
+
- app/views/tfa/tfas/show.html.erb
|
54
|
+
- config/routes.rb
|
55
|
+
- db/migrate/9999999999_create_tfas.rb
|
56
|
+
- lib/2fa.rb
|
57
|
+
- lib/TFA.rb
|
58
|
+
- lib/TFA/engine.rb
|
59
|
+
- lib/TFA/twilio.rb
|
60
|
+
- lib/TFA/version.rb
|
61
|
+
- readme.md
|
62
|
+
homepage: https://github.com/Matthiasclee/2fa
|
48
63
|
licenses: []
|
49
64
|
metadata: {}
|
50
65
|
post_install_message:
|
@@ -65,5 +80,5 @@ requirements: []
|
|
65
80
|
rubygems_version: 3.1.6
|
66
81
|
signing_key:
|
67
82
|
specification_version: 4
|
68
|
-
summary:
|
83
|
+
summary: Two factor authentication in rails apps made easy
|
69
84
|
test_files: []
|