2fa 0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a13cbb9ba023f654a9a4e445a7c44046bcaeeca6e0d0d9be07da4b8770b9fda8
4
- data.tar.gz: dd24c3842ed99b5f5f1dd26389f5fc274a21105eb3c059dfac88ccfea96db2ca
3
+ metadata.gz: 581baafb237e2897facab0f9d6a21a5ce8ed2be2f0f32fd85d2b16de58d32e1c
4
+ data.tar.gz: e9fa994ebf0cf2d2b6cd9b97859e28e90147e4fc3e62d47650855e2b27437dba
5
5
  SHA512:
6
- metadata.gz: 31b643f5c061582329f7f6bf402cb49e9431f851eaa6ca5b55d78bcbb2c5e2892d08144fb3cc3046802672896655e0d1021c4379ad453889341b238842cbd7cd
7
- data.tar.gz: 5744d0084ccf69d870b876e3564ab1135e621115ed2b86445c1f0fe99b9b2ac1882b13df6e85aeb6df8b29f11b1404ad5e5283d66f96c387ad14804d2a2c6d46
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,8 @@
1
+ module TFA
2
+ class TfasController < ActionController::Base
3
+ def show
4
+ @tfa = Tfa.find_by(id: params[:id])
5
+ head 404 if @tfa.used
6
+ end
7
+ end
8
+ 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,4 @@
1
+ module TFA
2
+ class Tfa < ActiveRecord::Base
3
+ end
4
+ 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
@@ -0,0 +1,3 @@
1
+ TFA::Engine.routes.draw do
2
+ get '/:id/verify', to: 'tfas#show', as: :tfa_verify
3
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTfas < ActiveRecord::Migration[6.1]
2
+ def change
3
+ create_table :tfa_tfas do |t|
4
+ t.integer :code
5
+ t.string :phone
6
+ t.string :after
7
+ t.boolean :used
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
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
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'twilio-ruby'
2
+ require 'rails'
3
+ require_relative "TFA/engine.rb"
4
+ require_relative "TFA/twilio.rb"
5
+
6
+ module TFA
7
+ end
8
+
9
+ Tfa = TFA
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: '0'
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-04 00:00:00.000000000 Z
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: '0'
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: '0'
41
- description: A gem for two factor authentication in rails apps
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
- homepage:
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: A gem for two factor authentication in rails apps
83
+ summary: Two factor authentication in rails apps made easy
69
84
  test_files: []