userbin 0.4.5 → 1.0.0

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.
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