userbin 0.4.5 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +87 -111
  3. data/lib/userbin.rb +18 -39
  4. data/lib/userbin/configuration.rb +14 -16
  5. data/lib/userbin/errors.rb +5 -0
  6. data/lib/userbin/helpers.rb +83 -0
  7. data/lib/userbin/jwt.rb +40 -0
  8. data/lib/userbin/models/base.rb +24 -0
  9. data/lib/userbin/models/challenge.rb +4 -0
  10. data/lib/userbin/models/session.rb +11 -0
  11. data/lib/userbin/models/user.rb +9 -0
  12. data/lib/userbin/request.rb +99 -0
  13. data/lib/userbin/utils.rb +28 -0
  14. data/lib/userbin/version.rb +1 -1
  15. data/spec/configuration_spec.rb +8 -0
  16. data/spec/fixtures/vcr_cassettes/session_create.yml +47 -0
  17. data/spec/fixtures/vcr_cassettes/session_refresh.yml +47 -0
  18. data/spec/fixtures/vcr_cassettes/session_verify.yml +47 -0
  19. data/spec/fixtures/vcr_cassettes/user_find.yml +44 -0
  20. data/spec/fixtures/vcr_cassettes/user_find_non_existing.yml +42 -0
  21. data/spec/fixtures/vcr_cassettes/user_import.yml +46 -0
  22. data/spec/fixtures/vcr_cassettes/user_update.yml +47 -0
  23. data/spec/helpers_spec.rb +38 -0
  24. data/spec/jwt_spec.rb +67 -0
  25. data/spec/models/session_spec.rb +33 -0
  26. data/spec/models/user_spec.rb +43 -0
  27. data/spec/spec_helper.rb +11 -0
  28. data/spec/utils_spec.rb +36 -0
  29. metadata +128 -36
  30. data/lib/userbin/authentication.rb +0 -132
  31. data/lib/userbin/basic_auth.rb +0 -31
  32. data/lib/userbin/current.rb +0 -17
  33. data/lib/userbin/events.rb +0 -40
  34. data/lib/userbin/rails/auth_helpers.rb +0 -22
  35. data/lib/userbin/railtie.rb +0 -14
  36. data/lib/userbin/session.rb +0 -26
  37. data/lib/userbin/userbin.rb +0 -104
  38. data/spec/session_spec.rb +0 -40
  39. data/spec/userbin_spec.rb +0 -102
@@ -0,0 +1,40 @@
1
+ require 'jwt'
2
+
3
+ module Userbin
4
+ class JWT
5
+ attr_reader :header
6
+ attr_reader :payload
7
+
8
+ def initialize(jwt)
9
+ begin
10
+ raise Userbin::SecurityError, 'Empty JWT' unless jwt
11
+ @payload = ::JWT.decode(jwt, Userbin.config.api_secret, true) do |header|
12
+ @header = header.with_indifferent_access
13
+ Userbin.config.api_secret # used by the 'key finder' in the JWT gem
14
+ end.with_indifferent_access
15
+ rescue ::JWT::DecodeError => e
16
+ raise Userbin::SecurityError.new(e)
17
+ end
18
+ end
19
+
20
+ def expired?
21
+ Time.now.utc > Time.at(@header['exp']).utc
22
+ end
23
+
24
+ def to_json
25
+ @payload
26
+ end
27
+
28
+ def to_token
29
+ ::JWT.encode(@payload, Userbin.config.api_secret, "HS256", @header)
30
+ end
31
+
32
+ def app_id
33
+ @header['aud']
34
+ end
35
+
36
+ def merge!(payload = {})
37
+ @payload.merge!(payload)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ require 'her'
2
+
3
+ module Userbin
4
+ class Base
5
+ include Her::Model
6
+ use_api Userbin::API
7
+
8
+ METHODS.each do |method|
9
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
10
+ def self.instance_#{method}(action)
11
+ instance_custom(:#{method}, action)
12
+ end
13
+ RUBY
14
+ end
15
+
16
+ def self.instance_custom(method, action)
17
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
18
+ def #{action}(params={})
19
+ self.class.#{method}("\#{request_path}/#{action}", params)
20
+ end
21
+ RUBY
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,4 @@
1
+ module Userbin
2
+ class Challenge < Base
3
+ end
4
+ end
@@ -0,0 +1,11 @@
1
+ module Userbin
2
+ class Session < Base
3
+ primary_key :token
4
+ instance_post :refresh
5
+ instance_post :verify
6
+
7
+ def expired?
8
+ Userbin::JWT.new(token).expired?
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ module Userbin
2
+ class User < Base
3
+ custom_post :import
4
+ instance_post :lock
5
+ instance_post :unlock
6
+
7
+ has_many :sessions
8
+ end
9
+ end
@@ -0,0 +1,99 @@
1
+ module Userbin
2
+ module Request
3
+
4
+ def self.client_user_agent
5
+ @uname ||= get_uname
6
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
7
+
8
+ {
9
+ :bindings_version => Userbin::VERSION,
10
+ :lang => 'ruby',
11
+ :lang_version => lang_version,
12
+ :platform => RUBY_PLATFORM,
13
+ :publisher => 'userbin',
14
+ :uname => @uname
15
+ }
16
+ end
17
+
18
+ def self.get_uname
19
+ `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
20
+ rescue Errno::ENOMEM => ex # couldn't create subprocess
21
+ "uname lookup failed"
22
+ end
23
+
24
+ #
25
+ # Faraday middleware
26
+ #
27
+ module Middleware
28
+ # Sets credentials dynamically, allowing them to change between requests.
29
+ #
30
+ class BasicAuth < Faraday::Middleware
31
+ def initialize(app, api_secret)
32
+ super(app)
33
+ @api_secret = api_secret
34
+ end
35
+
36
+ def call(env)
37
+ value = Base64.encode64("#{@api_secret || Userbin.config.api_secret}:")
38
+ value.gsub!("\n", '')
39
+ env[:request_headers]["Authorization"] = "Basic #{value}"
40
+ @app.call(env)
41
+ end
42
+ end
43
+
44
+ # Adds details about current environment
45
+ #
46
+ class EnvironmentHeaders < Faraday::Middleware
47
+ def call(env)
48
+ begin
49
+ env[:request_headers]["X-Userbin-Client-User-Agent"] =
50
+ MultiJson.encode(Userbin::Request.client_user_agent)
51
+ rescue => error; end
52
+
53
+ env[:request_headers]["User-Agent"] =
54
+ "Userbin/v1 RubyBindings/#{Userbin::VERSION}"
55
+
56
+ @app.call(env)
57
+ end
58
+ end
59
+
60
+ # Adds request context like IP address and user agent to any request.
61
+ #
62
+ class ContextHeaders < Faraday::Middleware
63
+ def call(env)
64
+ userbin_headers = RequestStore.store.fetch(:userbin_headers, [])
65
+ userbin_headers.each do |key, value|
66
+ header =
67
+ "X-Userbin-#{key.to_s.gsub('_', '-').gsub(/\w+/) {|m| m.capitalize}}"
68
+ env[:request_headers][header] = value
69
+ end
70
+ @app.call(env)
71
+ end
72
+ end
73
+
74
+ class JSONParser < Her::Middleware::DefaultParseJSON
75
+ # This method is triggered when the response has been received. It modifies
76
+ # the value of `env[:body]`.
77
+ #
78
+ # @param [Hash] env The response environment
79
+ # @private
80
+ def on_complete(env)
81
+ env[:body] = '{}' if [204, 405].include?(env[:status])
82
+ env[:body] = case env[:status]
83
+ when 403
84
+ raise Userbin::ForbiddenError.new(
85
+ MultiJson.decode(env[:body])['message'])
86
+ when 419
87
+ raise Userbin::UserUnauthorizedError.new(
88
+ MultiJson.decode(env[:body])['message'])
89
+ when 400..599
90
+ raise Userbin::Error.new(MultiJson.decode(env[:body])['message'])
91
+ else
92
+ parse(env[:body])
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
@@ -0,0 +1,28 @@
1
+ # TODO: scope Userbin::Utils
2
+
3
+ module Userbin
4
+ class << self
5
+ def setup_api(api_secret = nil)
6
+ api_endpoint = ENV.fetch('USERBIN_API_ENDPOINT') {
7
+ "https://secure.userbin.com/v1"
8
+ }
9
+
10
+ Her::API.setup url: api_endpoint do |c|
11
+ c.use Userbin::Request::Middleware::BasicAuth, api_secret
12
+ c.use Userbin::Request::Middleware::EnvironmentHeaders
13
+ c.use Userbin::Request::Middleware::ContextHeaders
14
+ c.use FaradayMiddleware::EncodeJson
15
+ c.use Userbin::Request::Middleware::JSONParser
16
+ c.use Faraday::Adapter::NetHttp
17
+ end
18
+ end
19
+
20
+ def with_context(opts, &block)
21
+ return block.call unless opts
22
+ RequestStore.store[:userbin_headers] = {}
23
+ RequestStore.store[:userbin_headers][:ip] = opts[:ip] if opts[:ip]
24
+ RequestStore.store[:userbin_headers][:user_agent] = opts[:user_agent] if opts[:user_agent]
25
+ block.call
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Userbin
2
- VERSION = "0.4.5"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Userbin configuration' do
4
+ it 'sets API secret without using block' do
5
+ #Userbin.api_secret = 'secret'
6
+ #Userbin.config.api_secret.should == 'secret'
7
+ end
8
+ end
@@ -0,0 +1,47 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://secretkey:@secure.userbin.com/v1/users/user-2412/sessions
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"user":{"email":"valid@example.com"}}'
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Content-Type:
13
+ - application/json
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 201
21
+ message: 'Created '
22
+ headers:
23
+ Content-Type:
24
+ - application/json
25
+ Content-Length:
26
+ - '716'
27
+ X-Ua-Compatible:
28
+ - IE=Edge
29
+ Etag:
30
+ - '"c323dc2244006efcc5629936b73749ff"'
31
+ Cache-Control:
32
+ - max-age=0, private, must-revalidate
33
+ Server:
34
+ - WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)
35
+ Date:
36
+ - Wed, 07 May 2014 16:21:05 GMT
37
+ Connection:
38
+ - Keep-Alive
39
+ Set-Cookie:
40
+ - _ubt=; expires=Thu, 01-Jan-1970 00:00:00 GMT
41
+ body:
42
+ encoding: UTF-8
43
+ string: '{"id":"S2odxReZnGqhqxPaQ7V7kNkLoXkGZPFz","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6InVzZXItMjQxMiIsInN1YiI6IlMyb2R4UmVabkdxaHF4UGFRN1Y3a05rTG9Ya0daUEZ6IiwiYXVkIjoiODAwMDAwMDAwMDAwMDAwIiwiZXhwIjoxMzk5NDc5Njc1LCJpYXQiOjEzOTk0Nzk2NjUsImp0aSI6MH0.eyJjaGFsbGVuZ2UiOnsiaWQiOiJUVENqd3VyM3lwbTRUR1ZwWU43cENzTXFxOW9mWEVBSCIsInR5cGUiOiJvdHBfYXV0aGVudGljYXRvciJ9fQ.LT9mUzJEbsizbFxcpMo3zbms0aCDBzfgMbveMGSi1-s","user":{"id":"8Fpmj9asxpq4mwGzx48MP1EQhdWUHDeb","local_id":"user-2412","created_at":"2014-05-07T15:33:58Z","updated_at":"2014-05-07T16:04:56Z","email":"valid@example.com","username":"valid@example.com","name":"New
44
+ Name","first_name":null,"last_name":null,"image":null,"status":"ACTIVE","mfa_enabled":true}}'
45
+ http_version:
46
+ recorded_at: Wed, 07 May 2014 16:21:05 GMT
47
+ recorded_with: VCR 2.9.0
@@ -0,0 +1,47 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://secretkey:@secure.userbin.com/v1/sessions/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6InVzZXItMjQxMiIsInN1YiI6IlMyb2R4UmVabkdxaHF4UGFRN1Y3a05rTG9Ya0daUEZ6IiwiYXVkIjoiODAwMDAwMDAwMDAwMDAwIiwiZXhwIjoxMzk5NDc5Njc1LCJpYXQiOjEzOTk0Nzk2NjUsImp0aSI6MH0.eyJjaGFsbGVuZ2UiOnsiaWQiOiJUVENqd3VyM3lwbTRUR1ZwWU43cENzTXFxOW9mWEVBSCIsInR5cGUiOiJvdHBfYXV0aGVudGljYXRvciJ9fQ.LT9mUzJEbsizbFxcpMo3zbms0aCDBzfgMbveMGSi1-s/refresh
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"user":{"name":"New Name"}}'
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Content-Type:
13
+ - application/json
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 201
21
+ message: 'Created '
22
+ headers:
23
+ Content-Type:
24
+ - application/json
25
+ Content-Length:
26
+ - '716'
27
+ X-Ua-Compatible:
28
+ - IE=Edge
29
+ Etag:
30
+ - '"537ee28e9894002208f8f645f6dd2987"'
31
+ Cache-Control:
32
+ - max-age=0, private, must-revalidate
33
+ Server:
34
+ - WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)
35
+ Date:
36
+ - Wed, 07 May 2014 16:21:55 GMT
37
+ Connection:
38
+ - Keep-Alive
39
+ Set-Cookie:
40
+ - _ubt=; expires=Thu, 01-Jan-1970 00:00:00 GMT
41
+ body:
42
+ encoding: UTF-8
43
+ string: '{"id":"S2odxReZnGqhqxPaQ7V7kNkLoXkGZPFz","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6InVzZXItMjQxMiIsInN1YiI6IlMyb2R4UmVabkdxaHF4UGFRN1Y3a05rTG9Ya0daUEZ6IiwiYXVkIjoiODAwMDAwMDAwMDAwMDAwIiwiZXhwIjoxMzk5NDc5NzI1LCJpYXQiOjEzOTk0Nzk3MTUsImp0aSI6MX0.eyJjaGFsbGVuZ2UiOnsiaWQiOiJUVENqd3VyM3lwbTRUR1ZwWU43cENzTXFxOW9mWEVBSCIsInR5cGUiOiJvdHBfYXV0aGVudGljYXRvciJ9fQ.2UuUgl8jKkl1lhU0d2nSKmiw-Qzz130A9XJd7-swvv8","user":{"id":"8Fpmj9asxpq4mwGzx48MP1EQhdWUHDeb","local_id":"user-2412","created_at":"2014-05-07T15:33:58Z","updated_at":"2014-05-07T16:21:55Z","email":"valid@example.com","username":"valid@example.com","name":"New
44
+ Name","first_name":null,"last_name":null,"image":null,"status":"ACTIVE","mfa_enabled":true}}'
45
+ http_version:
46
+ recorded_at: Wed, 07 May 2014 16:21:55 GMT
47
+ recorded_with: VCR 2.9.0
@@ -0,0 +1,47 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://secretkey:@secure.userbin.com/v1/sessions/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6InVzZXItMjQxMiIsInN1YiI6IlMyb2R4UmVabkdxaHF4UGFRN1Y3a05rTG9Ya0daUEZ6IiwiYXVkIjoiODAwMDAwMDAwMDAwMDAwIiwiZXhwIjoxMzk5NDc5Njc1LCJpYXQiOjEzOTk0Nzk2NjUsImp0aSI6MH0.eyJjaGFsbGVuZ2UiOnsiaWQiOiJUVENqd3VyM3lwbTRUR1ZwWU43cENzTXFxOW9mWEVBSCIsInR5cGUiOiJvdHBfYXV0aGVudGljYXRvciJ9fQ.LT9mUzJEbsizbFxcpMo3zbms0aCDBzfgMbveMGSi1-s/verify
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"response":"017010"}'
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Content-Type:
13
+ - application/json
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ Accept:
17
+ - "*/*"
18
+ response:
19
+ status:
20
+ code: 201
21
+ message: 'Created '
22
+ headers:
23
+ Content-Type:
24
+ - application/json
25
+ Content-Length:
26
+ - '609'
27
+ X-Ua-Compatible:
28
+ - IE=Edge
29
+ Etag:
30
+ - '"27b68d405c62c2c4c2a23acafde76b26"'
31
+ Cache-Control:
32
+ - max-age=0, private, must-revalidate
33
+ Server:
34
+ - WEBrick/1.3.1 (Ruby/2.1.1/2014-02-24)
35
+ Date:
36
+ - Wed, 07 May 2014 16:22:55 GMT
37
+ Connection:
38
+ - Keep-Alive
39
+ Set-Cookie:
40
+ - _ubt=; expires=Thu, 01-Jan-1970 00:00:00 GMT
41
+ body:
42
+ encoding: UTF-8
43
+ string: '{"id":"S2odxReZnGqhqxPaQ7V7kNkLoXkGZPFz","token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImlzcyI6InVzZXItMjQxMiIsInN1YiI6IlMyb2R4UmVabkdxaHF4UGFRN1Y3a05rTG9Ya0daUEZ6IiwiYXVkIjoiODAwMDAwMDAwMDAwMDAwIiwiZXhwIjoxMzk5NDc5NzI1LCJpYXQiOjEzOTk0Nzk3MTUsImp0aSI6MX0.e30.IRwMbLDd2LqRHB_QZvzG47eTr5vPCBrdWI6xPOPa6x4","user":{"id":"8Fpmj9asxpq4mwGzx48MP1EQhdWUHDeb","local_id":"user-2412","created_at":"2014-05-07T15:33:58Z","updated_at":"2014-05-07T16:21:55Z","email":"valid@example.com","username":"valid@example.com","name":"New
44
+ Name","first_name":null,"last_name":null,"image":null,"status":"ACTIVE","mfa_enabled":true}}'
45
+ http_version:
46
+ recorded_at: Wed, 07 May 2014 16:22:55 GMT
47
+ recorded_with: VCR 2.9.0
@@ -0,0 +1,44 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://secretkey:@secure.userbin.com/v1/users/9RA2j3cYDxt8gefQUduKnxUxRRGy6Rz4
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - '*/*'
16
+ response:
17
+ status:
18
+ code: 200
19
+ message: OK
20
+ headers:
21
+ Date:
22
+ - Fri, 18 Apr 2014 23:16:44 GMT
23
+ Status:
24
+ - 200 OK
25
+ Connection:
26
+ - close
27
+ Content-Type:
28
+ - application/json
29
+ Content-Length:
30
+ - '328'
31
+ Set-Cookie:
32
+ - _ubt=; expires=Thu, 01-Jan-1970 00:00:00 GMT
33
+ X-Ua-Compatible:
34
+ - IE=Edge
35
+ Etag:
36
+ - '"73f5664d599cfbc71df7850ca66cda3d"'
37
+ Cache-Control:
38
+ - max-age=0, private, must-revalidate
39
+ body:
40
+ encoding: UTF-8
41
+ string: '{"id":"9RA2j3cYDxt8gefQUduKnxUxRRGy6Rz4","created_at":"2014-04-18T13:30:22Z","updated_at":"2014-04-18T13:33:51Z","email":"brissmyr@gmail.com","username":"brissmyr@gmail.com","name":null,"first_name":null,"last_name":null,"image":null,"status":"ACTIVE","email_verification_token":"NhFy8wBYccLsEoL-kAVt","identities":[]}'
42
+ http_version:
43
+ recorded_at: Fri, 18 Apr 2014 23:16:44 GMT
44
+ recorded_with: VCR 2.9.0
@@ -0,0 +1,42 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://secretkey:@secure.userbin.com/v1/users/non_existing
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - Faraday v0.9.0
12
+ Accept-Encoding:
13
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
14
+ Accept:
15
+ - '*/*'
16
+ response:
17
+ status:
18
+ code: 404
19
+ message: Not Found
20
+ headers:
21
+ Date:
22
+ - Fri, 18 Apr 2014 23:29:27 GMT
23
+ Status:
24
+ - 404 Not Found
25
+ Connection:
26
+ - close
27
+ Content-Type:
28
+ - application/json
29
+ Content-Length:
30
+ - '42'
31
+ Set-Cookie:
32
+ - _ubt=; expires=Thu, 01-Jan-1970 00:00:00 GMT
33
+ X-Ua-Compatible:
34
+ - IE=Edge
35
+ Cache-Control:
36
+ - no-cache
37
+ body:
38
+ encoding: UTF-8
39
+ string: '{"type":"not_found","message":"Not found"}'
40
+ http_version:
41
+ recorded_at: Fri, 18 Apr 2014 23:29:27 GMT
42
+ recorded_with: VCR 2.9.0