alterego 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^spec/.+_spec\.rb$})
11
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
12
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
13
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
14
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
15
+ watch('spec/spec_helper.rb') { "spec" }
16
+ watch('config/routes.rb') { "spec/routing" }
17
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
18
+ # Capybara request specs
19
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
20
+ end
21
+
@@ -1,22 +1,50 @@
1
1
  = AlterEgo
2
2
 
3
- AlterEgo is a Ruby gem for interacting with the {AlterEgo}[https://alteregoapp.com] API. AlterEgo adds an extra layer of security to your web app to thwart phishing attacks and breaches caused by insecure passwords. It generates temporary auth codes only accessible on users' mobile devices and makes them required for entry.
4
-
5
- == Requirements
6
-
7
- You will need an {AlterEgo}[https://alteregoapp.com] account. More detailed instructions about generating API keys and granting access here...
3
+ AlterEgo is a Ruby gem for integrating {AlterEgo}[https://alteregoapp.com] two-factor authentication into your web application. You'll need an {AlterEgo account}[https://alteregoapp.com/#signupfree] in order to register your {register your app}[https://alteregoapp.com/app/register] and get your "App Authentication ID".
8
4
 
9
5
  == Installation
10
6
 
11
7
  (sudo) gem install alterego
12
8
 
13
- == Usage
9
+ == Authorizing Your Application
10
+
11
+ In order to use AlterEgo, users of your application will need to sign up for their own {AlterEgo account}[https://alteregoapp.com/#signupfree]. You will need your users to authorize the connection between your application and {AlterEgo}[https://alteregoapp.com] by simply redirecting them:
12
+
13
+ redirect_to AlterEgo.authorization_url("your_app_id", "https://yourapp.com/alterego/callback")
14
+
15
+ {AlterEgo}[https://alteregoapp.com] imposes the following requirements on the +redirect_url+ value to ensure security. Be sure that your +redirect_url+ meets the following requirements:
16
+
17
+ - Must be served via HTTPS.
18
+ - Must be under the same domain (or a subdomain of) the website entered when registering your application with {AlterEgo}[https://alteregoapp.com].
19
+
20
+ Once authorized successfully, a POST request will be sent to the +redirect_url+ with a "key" parameter containing the API key for that user's {AlterEgo}[https://alteregoapp.com] account. Be sure to store this key somewhere, as you will need it to run API requests later. If authorization fails for some reason, an "error" parameter will be present in the POST request, containing an error message.
21
+
22
+ if params[:key]
23
+ current_user.alter_ego_key = params[:key]
24
+ elsif params[:error]
25
+ flash[:alert] = params[:error]
26
+ end
27
+
28
+ == Authenticating With AlterEgo
29
+
30
+ Once your users have authorized your application and you have retrieved an API key for their account, you can easily integrate two-factor authentication. To authenticate using {AlterEgo}[https://alteregoapp.com] (either as part of an existing login process, or as a stand-alone authentication system) simply prompt the user to input a valid {AlterEgo}[https://alteregoapp.com] passcode and verify it:
31
+
32
+ passcode = params[:alter_ego_passcode]
33
+ if AlterEgo.password(current_user.alter_ego_key, passcode)
34
+ # Passcode is valid.
35
+ ...
36
+ else
37
+ # Passcode is not valid.
38
+ ...
39
+ end
14
40
 
15
- Please refer to the {AlterEgo API Documentation}[https://alteregoapp.com/apidocs/v1.0/] for information.
41
+ {AlterEgo}[https://alteregoapp.com] does not provide any kind of error message or explanation as to why a passcode is not valid, so you will want to be sure and keep your error messages appropriately generic.
16
42
 
17
- ...
43
+ == Pinging The API
18
44
 
45
+ The {AlterEgo}[https://alteregoapp.com] API also has a method for pinging the API, in case you want to periodically check to ensure that your user's API keys are still valid. A successful ping to the API will always return "PONG!" as a response.
19
46
 
47
+ AlterEgo.ping(current_user.alter_ego_api_key)
20
48
 
21
49
  == Contributing to AlterEgo
22
50
 
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ Jeweler::Tasks.new do |gem|
18
18
  gem.homepage = "http://github.com/terra-firma/alterego"
19
19
  gem.license = "MIT"
20
20
  gem.summary = %Q{Ruby gem for interacting with the AlterEgo API.}
21
- gem.description = %Q{AlterEgo is a Ruby gem that provides a wrapper for interacting with the AlterEgo web app authentication service API.}
21
+ gem.description = %Q{AlterEgo is a Ruby gem for integrating two-factor authentication into your web application.}
22
22
  gem.email = "brian@terra-firma-design.com"
23
23
  gem.authors = ["Brian Getting"]
24
24
  # dependencies defined in Gemfile
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{alterego}
8
+ s.version = "0.0.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{Brian Getting}]
12
+ s.date = %q{2011-10-17}
13
+ s.description = %q{AlterEgo is a Ruby gem for integrating two-factor authentication into your web application.}
14
+ s.email = %q{brian@terra-firma-design.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Guardfile",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "alterego.gemspec",
28
+ "lib/alterego.rb",
29
+ "spec/alterego/alterego_spec.rb",
30
+ "spec/alterego/fixtures/error.json",
31
+ "spec/spec_helper.rb"
32
+ ]
33
+ s.homepage = %q{http://github.com/terra-firma/alterego}
34
+ s.licenses = [%q{MIT}]
35
+ s.require_paths = [%q{lib}]
36
+ s.rubygems_version = %q{1.8.8}
37
+ s.summary = %q{Ruby gem for interacting with the AlterEgo API.}
38
+
39
+ if s.respond_to? :specification_version then
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<json>, [">= 0"])
44
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
45
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
46
+ s.add_development_dependency(%q<guard-rspec>, [">= 0"])
47
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
48
+ s.add_development_dependency(%q<rcov>, [">= 0"])
49
+ s.add_development_dependency(%q<rspec>, [">= 0"])
50
+ else
51
+ s.add_dependency(%q<json>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
53
+ s.add_dependency(%q<fakeweb>, [">= 0"])
54
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
56
+ s.add_dependency(%q<rcov>, [">= 0"])
57
+ s.add_dependency(%q<rspec>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<json>, [">= 0"])
61
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
62
+ s.add_dependency(%q<fakeweb>, [">= 0"])
63
+ s.add_dependency(%q<guard-rspec>, [">= 0"])
64
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
65
+ s.add_dependency(%q<rcov>, [">= 0"])
66
+ s.add_dependency(%q<rspec>, [">= 0"])
67
+ end
68
+ end
69
+
@@ -3,4 +3,81 @@ require 'uri'
3
3
  require 'rubygems'
4
4
  require 'json'
5
5
 
6
- require 'alterego/api'
6
+ class AlterEgo
7
+
8
+ API_URL = "https://alteregoapp.com/api"
9
+ AUTH_URL = "http://alteregoapp.com/account/authorize-app"
10
+
11
+ # Generate an AlterEgo +authorization_url+.
12
+ # Returns a URL to redirect users to so that they will be prompted
13
+ # to enter their AlterEgo username and password to authorize a
14
+ # connection between your application and their AlterEgo account.
15
+ #
16
+ # If authorized successfully, a POST request will be sent to the
17
+ # +redirect_url+ with a "key" parameter containing the API key for
18
+ # that user's AlterEgo account. Be sure to store this key somewhere,
19
+ # as you will need it to run API requests later.
20
+ #
21
+ # If authorization fails for some reason, an "error" parameter will
22
+ # be present in the POST request, containing an error message.
23
+ #
24
+ # == A note about callback URL's:
25
+ #
26
+ # AlterEgo imposes the following requirements on the +redirect_url+ value
27
+ # to ensure security. Be sure that your +redirect_url+ meets the following
28
+ # requirements:
29
+ #
30
+ # - Must be served via HTTPS.
31
+ # - Must be under the same domain (or a subdomain of) the website entered
32
+ # when registering your application with AlterEgo.
33
+ #
34
+ # == Example
35
+ #
36
+ # redirect_to AlterEgo.authorization_url("12345","https://example.com/callback")
37
+ #
38
+ def self.authorization_url(app_id, redirect_url)
39
+ # TODO: Encode the redirect_url.
40
+ "#{AUTH_URL}?id=#{app_id}&redirect_url=#{URI.escape(redirect_url, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}"
41
+ end
42
+
43
+ # Ping the AlterEgo API.
44
+ # Returns either "PONG!" (indicating a successful connection to the API) or
45
+ # an error.
46
+ #
47
+ # == Example
48
+ #
49
+ # AlterEgo.ping("valid_api_key")
50
+ #
51
+ def self.ping(key)
52
+ call("#{API_URL}/check/ping.json?key=#{key}")
53
+ end
54
+
55
+ # Check an AlterEgo passcode.
56
+ # Returns +true+ if the passcode if valid, and +false+ if the passcode is not
57
+ # valid.
58
+ #
59
+ # == Example
60
+ #
61
+ # AlterEgo.password("valid_api_key", "passcode")
62
+ #
63
+ def self.password(key, passcode)
64
+ call("#{API_URL}/check/password.json?key=#{key}&pass=#{passcode}")
65
+ end
66
+
67
+ private
68
+
69
+ def self.call(url) # :nodoc:
70
+ response = Net::HTTP.get_response(URI.parse(url))
71
+ raise AlterEgo::APIError.new(response.body) if response.code.to_i == 500
72
+ return nil if response.body == ''
73
+ response.body
74
+ end
75
+
76
+ class APIError < StandardError
77
+ def initialize(response) # :nodoc:
78
+ error = JSON.parse(response)
79
+ super "<#{error['code']}> #{error['name']}: #{error['message']}"
80
+ end
81
+ end
82
+
83
+ end
@@ -1,7 +1,52 @@
1
1
  require 'spec_helper'
2
2
 
3
- module AlterEgo
4
- describe API do
3
+ describe AlterEgo do
5
4
 
5
+ describe "authorization_url" do
6
+ it "should require an app_id and redirect_url" do
7
+ expect {AlterEgo.authorization_url}.should raise_error(ArgumentError)
8
+ expect {AlterEgo.authorization_url(@app_id)}.should raise_error(ArgumentError)
9
+ expect {AlterEgo.authorization_url(@app_id, @callback_url)}.should_not raise_error(ArgumentError)
10
+ end
11
+
12
+ it "should generate an authorization URL" do
13
+ @callback_url = 'https://test.com/callback'
14
+ url = AlterEgo.authorization_url(@app_id, @callback_url)
15
+ url.should eq("#{AlterEgo::AUTH_URL}?id=#{@app_id}&redirect_url=#{URI.escape(@callback_url, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}")
16
+ end
17
+ end
18
+
19
+ describe "API" do
20
+ API_URL = "http://alteregoapp.com:443/api/check"
21
+
22
+ it "should require a key when pinging" do
23
+ expect {AlterEgo.ping}.should raise_error(ArgumentError)
24
+ end
25
+
26
+ it "should return 'PONG!' when pinging successfully" do
27
+ FakeWeb.register_uri(:get, "#{API_URL}/ping.json?key=#{@api_key}", :body => "PONG!")
28
+ AlterEgo.ping(@api_key).should eq("PONG!")
29
+ end
30
+
31
+ it "should require a key and passcode when checking password" do
32
+ expect {AlterEgo.password}.should raise_error(ArgumentError)
33
+ expect {AlterEgo.password(@api_key)}.should raise_error(ArgumentError)
34
+ end
35
+
36
+ it "should return true when the passcode matches" do
37
+ FakeWeb.register_uri(:get, "#{API_URL}/password.json?key=#{@api_key}&pass=#{@passcode}", :body => "true")
38
+ AlterEgo.password(@api_key, @passcode).should eq("true")
39
+ end
40
+
41
+ it "should return false when the passcode doesn't match" do
42
+ FakeWeb.register_uri(:get, "#{API_URL}/password.json?key=#{@api_key}&pass=#{@passcode}", :body => "false")
43
+ AlterEgo.password(@api_key, @passcode).should eq("false")
44
+ end
45
+
46
+ it "should respond to 500 errors from the API" do
47
+ FakeWeb.register_uri(:get, "#{API_URL}/password.json?key=#{@api_key}&pass=#{@passcode}", :body => @error, :status => ["500", "Internal Server Error"])
48
+ expect {AlterEgo.password(@api_key, @passcode)}.should raise_error(AlterEgo::APIError)
49
+ end
6
50
  end
7
- end
51
+
52
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "status": "error",
3
+ "code": 0,
4
+ "name": "Error",
5
+ "message": "A random error occurred"
6
+ }
@@ -6,5 +6,10 @@ FakeWeb.allow_net_connect = false
6
6
  RSpec.configure do |config|
7
7
  config.before(:each) do
8
8
  FakeWeb.clean_registry
9
+ @app_id = 'valid-app-id'
10
+ @api_key = 'valid-api-key'
11
+ @callback_url = 'https://example.com/callback'
12
+ @passcode = 'alter-ego-passcode'
13
+ @error = File.read(File.expand_path('spec/alterego/fixtures/error.json'))
9
14
  end
10
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alterego
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-12 00:00:00.000000000Z
12
+ date: 2011-10-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &2153544280 !ruby/object:Gem::Requirement
16
+ requirement: &2153522520 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2153544280
24
+ version_requirements: *2153522520
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &2153540900 !ruby/object:Gem::Requirement
27
+ requirement: &2153520800 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2153540900
35
+ version_requirements: *2153520800
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: fakeweb
38
- requirement: &2153538600 !ruby/object:Gem::Requirement
38
+ requirement: &2153518960 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2153538600
46
+ version_requirements: *2153518960
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: guard-rspec
49
- requirement: &2153536960 !ruby/object:Gem::Requirement
49
+ requirement: &2153517220 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2153536960
57
+ version_requirements: *2153517220
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: jeweler
60
- requirement: &2153535960 !ruby/object:Gem::Requirement
60
+ requirement: &2153515440 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.6.4
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *2153535960
68
+ version_requirements: *2153515440
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rcov
71
- requirement: &2153534680 !ruby/object:Gem::Requirement
71
+ requirement: &2153512980 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *2153534680
79
+ version_requirements: *2153512980
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: rspec
82
- requirement: &2153532960 !ruby/object:Gem::Requirement
82
+ requirement: &2153510180 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,9 +87,9 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *2153532960
91
- description: AlterEgo is a Ruby gem that provides a wrapper for interacting with the
92
- AlterEgo web app authentication service API.
90
+ version_requirements: *2153510180
91
+ description: AlterEgo is a Ruby gem for integrating two-factor authentication into
92
+ your web application.
93
93
  email: brian@terra-firma-design.com
94
94
  executables: []
95
95
  extensions: []
@@ -99,13 +99,15 @@ extra_rdoc_files:
99
99
  files:
100
100
  - .document
101
101
  - Gemfile
102
+ - Guardfile
102
103
  - LICENSE.txt
103
104
  - README.rdoc
104
105
  - Rakefile
105
106
  - VERSION
107
+ - alterego.gemspec
106
108
  - lib/alterego.rb
107
- - lib/alterego/api.rb
108
109
  - spec/alterego/alterego_spec.rb
110
+ - spec/alterego/fixtures/error.json
109
111
  - spec/spec_helper.rb
110
112
  homepage: http://github.com/terra-firma/alterego
111
113
  licenses:
@@ -122,7 +124,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
124
  version: '0'
123
125
  segments:
124
126
  - 0
125
- hash: 2984477021801594771
127
+ hash: -3242706057364793623
126
128
  required_rubygems_version: !ruby/object:Gem::Requirement
127
129
  none: false
128
130
  requirements:
@@ -1,12 +0,0 @@
1
- module AlterEgo
2
- class API
3
- # TODO: Put this together...
4
- end
5
-
6
- class APIError < StandardError
7
- def initialize(error)
8
- super("<#{error.faultCode}> #{error.message}")
9
- end
10
- end
11
-
12
- end