visa 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f100a7183b849f640a7613dd135bf03d2b48e45
4
+ data.tar.gz: ba75001cfa1ecd0a285386a39431591f222edc30
5
+ SHA512:
6
+ metadata.gz: d8c99ae11ed0d4095bb671d4ca18594e01fc308ace4ec344e659e7319e238d3988858022c2e098ceb98e114899dadcbb18b05e3923d35f0ab34b3eeca9f89dcc
7
+ data.tar.gz: ba52559000d5691e7ddefe32ec34dbe6bf203086c96492975d8d5d525457977f19a940fdbff5d4bbce8d57f20284c74743f910eda63de2476f3ba8850b80e519
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ *.gem
15
+ *.sqlite
16
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Pat Allan, Inspire9
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Visa
2
+
3
+ Multi-token authentication for Rails apps. Built with Devise in mind, but can be used separately.
4
+
5
+ ## Installation
6
+
7
+ Something like the following should go in your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'visa', '~> 0.0.1'
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ TODO: Write usage instructions here
16
+
17
+ ## Contributing
18
+
19
+ 1. Fork it ( https://github.com/inspire9/visa/fork )
20
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
21
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
22
+ 4. Push to the branch (`git push origin my-new-feature`)
23
+ 5. Create a new Pull Request
24
+
25
+ ## Credits
26
+
27
+ Inspiration has come from [Lynn Dylan Hurley](https://github.com/lynndylanhurley)'s [devise_token_auth](https://github.com/lynndylanhurley/devise_token_auth) and [this post](http://www.brianauton.com/posts/token-authentication-devise.html) by [Brian Auton](https://github.com/brianauton).
28
+
29
+ ## Licence
30
+
31
+ Copyright (c) 2015, Visa is developed and maintained by Pat Allan and Inspire9, and is released under the open MIT Licence.
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,39 @@
1
+ class Visa::Token < ActiveRecord::Base
2
+ self.table_name = 'visa_tokens'
3
+
4
+ belongs_to :tokenable, polymorphic: true
5
+
6
+ validates :tokenable, presence: true
7
+ validates :client_id, presence: true, uniqueness: true
8
+ validates :encrypted_secret, presence: true
9
+
10
+ before_validation :set_client_id, on: :create
11
+ before_validation :set_secret, on: :create
12
+
13
+ attr_reader :secret
14
+
15
+ def self.find_by_credentials(client_id, secret)
16
+ token = find_by client_id: client_id
17
+ token && token.has_secret?(secret) ? token : nil
18
+ end
19
+
20
+ def has_secret?(secret)
21
+ BCrypt::Password.new(encrypted_secret) == secret
22
+ end
23
+
24
+ private
25
+
26
+ def encryption_cost
27
+ Visa.encryption_cost || 10
28
+ end
29
+
30
+ def set_client_id
31
+ self.client_id = SecureRandom.hex 8
32
+ end
33
+
34
+ def set_secret
35
+ @secret = SecureRandom.urlsafe_base64 31
36
+ self.encrypted_secret = BCrypt::Password.create @secret,
37
+ cost: encryption_cost
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize!
7
+ run Combustion::Application
@@ -0,0 +1,15 @@
1
+ class CreateTokens < ActiveRecord::Migration
2
+ def change
3
+ create_table :visa_tokens do |t|
4
+ t.string :tokenable_type, null: false
5
+ t.integer :tokenable_id, null: false
6
+ t.string :client_id, null: false
7
+ t.string :encrypted_secret, null: false
8
+ t.datetime :last_requested_at
9
+ t.timestamps null: false
10
+ end
11
+
12
+ add_index :visa_tokens, [:tokenable_type, :tokenable_id]
13
+ add_index :visa_tokens, :client_id, unique: true
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ require 'bcrypt'
2
+ require 'rack'
3
+
4
+ module Visa
5
+ mattr_accessor :encryption_cost, :request_header, :timeout
6
+ end
7
+
8
+ Visa.encryption_cost = 10
9
+ Visa.request_header = 'Authentication'
10
+ Visa.timeout = 14.days
11
+
12
+ require 'visa/engine'
13
+ require 'visa/request'
@@ -0,0 +1,3 @@
1
+ class Visa::Engine < Rails::Engine
2
+ engine_name :visa
3
+ end
@@ -0,0 +1,39 @@
1
+ class Visa::Request
2
+ delegate :tokenable, to: :token
3
+
4
+ def initialize(environment)
5
+ @environment = environment
6
+ end
7
+
8
+ def touch
9
+ token.touch :last_requested_at
10
+ end
11
+
12
+ def valid?
13
+ token.present? && not_too_old?
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :environment
19
+
20
+ def credentials
21
+ string = request.params['access_token'] ||
22
+ request.headers[Visa.request_header]
23
+
24
+ [string[0..15], string[16..57]]
25
+ end
26
+
27
+ def not_too_old?
28
+ time = token.last_requested_at
29
+ time.nil? || (time > Visa.timeout.ago)
30
+ end
31
+
32
+ def request
33
+ @request ||= Rack::Request.new environment
34
+ end
35
+
36
+ def token
37
+ @token ||= Visa::Token.find_by_credentials *credentials
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ class ApplicationController < ActionController::Base
2
+ private
3
+
4
+ def authenticate_user!
5
+ if user_signed_in?
6
+ visa_request.touch
7
+ else
8
+ render text: 'Unauthorised', status: 401
9
+ end
10
+ end
11
+
12
+ def current_user
13
+ visa_request.tokenable
14
+ end
15
+
16
+ def visa_request
17
+ @visa_request ||= Visa::Request.new request.env
18
+ end
19
+
20
+ def user_signed_in?
21
+ visa_request.valid?
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ class HomeController < ApplicationController
2
+ before_filter :authenticate_user!
3
+
4
+ def index
5
+ render text: 'OK'
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ class User < ActiveRecord::Base
2
+ #
3
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ root 'home#index'
3
+ end
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :users, force: true do |t|
3
+ t.string :email
4
+ t.timestamps null: false
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ *.log
File without changes
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Visa::Token, type: :model do
4
+ let(:token) { Visa::Token.create tokenable: User.create }
5
+
6
+ describe '.find_by_credentials' do
7
+ it 'returns the matching token' do
8
+ existing_token = Visa::Token.find_by_credentials token.client_id,
9
+ token.secret
10
+ expect(existing_token).to eq(token)
11
+ end
12
+
13
+ it 'returns nil when the client id is wrong' do
14
+ existing_token = Visa::Token.find_by_credentials 'foo', token.secret
15
+ expect(existing_token).to be_nil
16
+ end
17
+
18
+ it 'returns nil when the secret is wrong' do
19
+ existing_token = Visa::Token.find_by_credentials token.client_id,
20
+ 'foo'
21
+ expect(existing_token).to be_nil
22
+ end
23
+ end
24
+
25
+ describe '#client_id' do
26
+ it 'is populated on creation' do
27
+ expect(token.client_id).to be_present
28
+ end
29
+ end
30
+
31
+ describe '#secret' do
32
+ it 'is populated on creation' do
33
+ expect(token.secret).to be_present
34
+ end
35
+
36
+ it 'is not persisted' do
37
+ existing_token = Visa::Token.find token.id
38
+ expect(existing_token.secret).to be_nil
39
+ end
40
+ end
41
+
42
+ describe '#has_secret?' do
43
+ it 'matches against the original secret' do
44
+ expect(token.has_secret?(token.secret)).to eq(true)
45
+ end
46
+
47
+ it 'returns false with different secrets' do
48
+ expect(token.has_secret?('foo')).to eq(false)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Request integration', type: :request do
4
+ let(:token) { Visa::Token.create tokenable: User.create }
5
+
6
+ it 'accepts valid tokens' do
7
+ get '/', access_token: "#{token.client_id}#{token.secret}"
8
+
9
+ expect(response.status).to eq(200)
10
+ end
11
+
12
+ it 'returns 401 when the token is invalid' do
13
+ get '/', access_token: "#{token.client_id}this-is-invalid"
14
+
15
+ expect(response.status).to eq(401)
16
+ end
17
+
18
+ it 'returns 401 when the token has not been used in two weeks' do
19
+ token.update_column :last_requested_at, 15.days.ago
20
+
21
+ get '/', access_token: "#{token.client_id}#{token.secret}"
22
+
23
+ expect(response.status).to eq(401)
24
+ end
25
+
26
+ it 'updates the last_requested_at column' do
27
+ get '/', access_token: "#{token.client_id}#{token.secret}"
28
+
29
+ token.reload
30
+
31
+ expect(token.last_requested_at).to_not be_nil
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup :default, :development
4
+
5
+ require 'rails'
6
+ require 'combustion'
7
+ require 'visa'
8
+
9
+ Combustion.initialize! :active_record
10
+
11
+ require 'rspec/rails'
12
+
13
+ Visa.encryption_cost = 1
14
+
15
+ RSpec.configure do |config|
16
+ config.use_transactional_fixtures = true
17
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Visa::Request do
4
+ describe '#valid?' do
5
+ let(:environment) { {'rack.input' => StringIO.new('')} }
6
+ let(:request) { Visa::Request.new environment }
7
+
8
+ before :each do
9
+ environment['QUERY_STRING'] = <<-STR
10
+ access_token=1234567890123456789012345678901234567890123456789012345678
11
+ STR
12
+
13
+ allow(Visa::Token).to receive(:find_by_credentials).and_return(nil)
14
+ end
15
+
16
+ it 'sources credentials from the access_token parameter' do
17
+ expect(Visa::Token).to receive(:find_by_credentials).
18
+ with('1234567890123456', '789012345678901234567890123456789012345678').
19
+ and_return(nil)
20
+
21
+ request.valid?
22
+ end
23
+
24
+ it 'returns true when a matching token is found' do
25
+ allow(Visa::Token).to receive(:find_by_credentials).
26
+ and_return(double('token', last_requested_at: nil))
27
+
28
+ expect(request).to be_valid
29
+ end
30
+
31
+ it 'returns true when a matching token is less than two weeks old' do
32
+ allow(Visa::Token).to receive(:find_by_credentials).
33
+ and_return(double('token', last_requested_at: 13.days.ago))
34
+
35
+ expect(request).to be_valid
36
+ end
37
+
38
+ it 'returns false when no token is found' do
39
+ allow(Visa::Token).to receive(:find_by_credentials).
40
+ and_return(nil)
41
+
42
+ expect(request).to_not be_valid
43
+ end
44
+
45
+ it 'returns false when a matching token is more than two weeks old' do
46
+ allow(Visa::Token).to receive(:find_by_credentials).
47
+ and_return(double('token', last_requested_at: 15.days.ago))
48
+
49
+ expect(request).to_not be_valid
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |spec|
3
+ spec.name = 'visa'
4
+ spec.version = '0.0.1'
5
+ spec.authors = ['Pat Allan']
6
+ spec.email = ['pat@freelancing-gods.com']
7
+ spec.summary = %q{Multi-token authentication for Rails apps.}
8
+ spec.description = %q{Multi-token authentication Rails engine.}
9
+ spec.homepage = 'https://github.com/inspire9/visa'
10
+ spec.license = 'MIT'
11
+
12
+ spec.files = `git ls-files -z`.split("\x0")
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_runtime_dependency 'bcrypt'
18
+ spec.add_runtime_dependency 'rack'
19
+ spec.add_runtime_dependency 'rails', '~> 4.0'
20
+
21
+ spec.add_development_dependency 'combustion', '0.5.1'
22
+ spec.add_development_dependency 'rspec-rails', '~> 3.1'
23
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
24
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: visa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bcrypt
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: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: combustion
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.3'
97
+ description: Multi-token authentication Rails engine.
98
+ email:
99
+ - pat@freelancing-gods.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - app/models/visa/token.rb
110
+ - config.ru
111
+ - db/migrate/1_create_tokens.rb
112
+ - lib/visa.rb
113
+ - lib/visa/engine.rb
114
+ - lib/visa/request.rb
115
+ - spec/internal/app/controllers/application_controller.rb
116
+ - spec/internal/app/controllers/home_controller.rb
117
+ - spec/internal/app/models/user.rb
118
+ - spec/internal/config/database.yml
119
+ - spec/internal/config/routes.rb
120
+ - spec/internal/db/schema.rb
121
+ - spec/internal/log/.gitignore
122
+ - spec/internal/public/favicon.ico
123
+ - spec/models/visa/token_spec.rb
124
+ - spec/requests/requests_spec.rb
125
+ - spec/spec_helper.rb
126
+ - spec/visa/request_spec.rb
127
+ - visa.gemspec
128
+ homepage: https://github.com/inspire9/visa
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.2.2
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Multi-token authentication for Rails apps.
152
+ test_files:
153
+ - spec/internal/app/controllers/application_controller.rb
154
+ - spec/internal/app/controllers/home_controller.rb
155
+ - spec/internal/app/models/user.rb
156
+ - spec/internal/config/database.yml
157
+ - spec/internal/config/routes.rb
158
+ - spec/internal/db/schema.rb
159
+ - spec/internal/log/.gitignore
160
+ - spec/internal/public/favicon.ico
161
+ - spec/models/visa/token_spec.rb
162
+ - spec/requests/requests_spec.rb
163
+ - spec/spec_helper.rb
164
+ - spec/visa/request_spec.rb