firebase_token_auth 0.9.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca7bb6f1b60938f8c7f4d37120e1cea83bb8b6a09ed2b1094a69f8f89649fc18
4
- data.tar.gz: 1d3adda763efeed626ed051b654dbba735e38af56c59d970e4b72d99ff4661d5
3
+ metadata.gz: dff32ef2301d4eb08f46ac3bd840f4c3e9adf5cd7f37c59c79d53b0dfbccdf1b
4
+ data.tar.gz: 5102df68ce6e5f516d044c4af295c92030a50fa226fd510cac996e72a36553f8
5
5
  SHA512:
6
- metadata.gz: c9c8edc8f21d3702239d1b4cc82b4c586c595147e99de741c089a5c25ad23ef5e8fc8b5dcb6b91ea59ca97e4b50858ed1572b67aebc4b26d3ba9aa5b1ce22281
7
- data.tar.gz: 3aeb0e99cb6684b7a2f54b47e834aa4613011399ea82ad7e1d5a7b0fb34f61fe77955b06fba76d5ab56093c6a2ebc7e7146895f37d79e711972a724e1ce909ae
6
+ metadata.gz: f659ddb2edf477b495a2be519cef6c96b5c083737521c60016a80bfa28264a67b4a8a054d12b1be929d1e0dc56ab033a504451efb36887bbb2770a2be0700707
7
+ data.tar.gz: 6d15331ff417a6fc5d1c45ebfbb44e830ea95aebf3535b6930bb61f045322fc55c79be42975ae8003c0f264985ba0506d00b981c89a395a16a50f1c2333318fb
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  .rspec_status
13
13
 
14
14
  Gemfile.lock
15
+ .ruby-version
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # FirebaseTokenAuth
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/firebase_token_auth`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ FirebaseTokenAuth is an Firebase Auth Client. It supports below.
4
+ - verify id_token method
5
+ - create custom token
6
+ - fetch user info by uid/email
6
7
 
7
8
  ## Installation
8
9
 
@@ -22,17 +23,106 @@ Or install it yourself as:
22
23
 
23
24
  ## Usage
24
25
 
25
- TODO: Write usage instructions here
26
+ on Rails, config/initializers/firebase_token_auth.rb
27
+ ```ruby
28
+ FirebaseTokenAuth.configure do |config|
29
+ ## for id_token_verify
30
+ # firebase web console => project settings => general => project ID
31
+ config.project_id = "your_project_id" # required
32
+
33
+ # firebase web console => project settings => service account => firebase admin sdk => generate new private key
34
+ # pass string of path to credential file to config.json_key_io
35
+ config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
36
+ # Or content of json key file wrapped with StringIO
37
+ # config.json_key_io = StringIO.new('{ ... }')
38
+
39
+ # Or set environment variables
40
+ # ENV['GOOGLE_ACCOUNT_TYPE'] = 'service_account'
41
+ # ENV['GOOGLE_CLIENT_ID'] = '000000000000000000000'
42
+ # ENV['GOOGLE_CLIENT_EMAIL'] = 'xxxx@xxxx.iam.gserviceaccount.com'
43
+ # ENV['GOOGLE_PRIVATE_KEY'] = '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n\'
44
+ end
45
+ ```
46
+ for more detail. see [here](https://github.com/googleapis/google-auth-library-ruby#example-service-account).
26
47
 
27
- ## Development
48
+ ### token verify
49
+ ```ruby
50
+ require 'firebase_token_auth'
51
+
52
+ FirebaseTokenAuth.configure do |config|
53
+ config.project_id = 'your_project_id'
54
+ end
55
+
56
+ client = Firebase.build
57
+ result = client.verify_id_token(id_token)
58
+
59
+ puts result.uid
60
+ # => "hMPHt8RyDpOsHi1oH5XaVirSYyq2"
61
+
62
+ puts result.id_token.payload # you can see decoded id_token payload
63
+ # => {"iss"=>"https://securetoken.google.com/<your_project_id>",
64
+ # "aud"=>"<your_project_id>",
65
+ # "auth_time"=>1594494935,
66
+ # "user_id"=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
67
+ # "sub"=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
68
+ # "iat"=>1594494935,
69
+ # "exp"=>1594498535,
70
+ # "email"=>"<your_user_email>",
71
+ # "email_verified"=>false,
72
+ # "firebase"=>{"identities"=>{"email"=>["<your_user_email>"]}, "sign_in_provider"=>"custom"}}
73
+
74
+ puts result.id_token.header
75
+ # => {"alg"=>"RS256", "kid"=>"7623e10a045140f1cfd4be0466cf80352b59f81e", "typ"=>"JWT"}
76
+ ```
28
77
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
78
+ ### custom token create
79
+ ```ruby
80
+ require 'firebase_token_auth'
30
81
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
82
+ FirebaseTokenAuth.configure do |config|
83
+ config.project_id = 'your_project_id'
84
+ config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
85
+ end
86
+
87
+ client = FirebaseTokenAuth.new
88
+ c_token = client.create_custom_token(test_uid)
89
+ puts c_token
90
+ # => "eyJhbGciOXXX.eyJpc3MiOiJmaXJlYmFzXXXX.v7y7LoBXXXXX" # dummy
91
+ ```
92
+
93
+ ### fetch users info from firebase
94
+ ```ruby
95
+ require 'firebase_token_auth'
96
+
97
+ FirebaseTokenAuth.configure do |config|
98
+ config.project_id = 'your_project_id'
99
+ config.json_key_io = "#{Rails.root}/path/to/service_account_credentials.json"
100
+ end
101
+
102
+ client = FirebaseTokenAuth.new
103
+ result = client.user_search_by_email(test_user_email)
104
+ # result = client.user_search_by_uid(test_uid)
105
+ puts result
106
+ # => [{:created_at=>1594132097140,
107
+ # :custom_auth=>true,
108
+ # :disabled=>false,
109
+ # :email=>"<your_user_email>",
110
+ # :email_verified=>false,
111
+ # :last_login_at=>1594495792373,
112
+ # :local_id=>"hMPHt8RyDpOsHi1oH5XaVirSYyq2",
113
+ # :password_hash=>"REDACTED",
114
+ # :password_updated_at=>1594132097140,
115
+ # :provider_user_info=>
116
+ # [{:email=>"<your_user_email>",
117
+ # :federated_id=>"<your_user_email>",
118
+ # :provider_id=>"password",
119
+ # :raw_id=>"<your_user_email>"}],
120
+ # :valid_since=>1594132097}]
121
+ ```
32
122
 
33
123
  ## Contributing
34
124
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/firebase_token_auth.
125
+ Bug reports and pull requests are welcome on GitHub at https://github.com/miyataka/firebase_token_auth.
36
126
 
37
127
 
38
128
  ## License
@@ -5,6 +5,7 @@ require 'jwt'
5
5
  require 'firebase_token_auth/public_key_manager'
6
6
  require 'firebase_token_auth/validator'
7
7
  require 'firebase_token_auth/admin_client'
8
+ require 'firebase_token_auth/exceptions'
8
9
 
9
10
  module FirebaseTokenAuth
10
11
  ALGORITHM = 'RS256'.freeze
@@ -25,7 +26,7 @@ module FirebaseTokenAuth
25
26
  end
26
27
 
27
28
  def verify_id_token(id_token, options = {})
28
- raise if id_token.nil? || id_token.empty?
29
+ raise ArgumentError, 'Firebase ID token must not null or blank strings.' if id_token.nil? || id_token.empty?
29
30
 
30
31
  public_key_id, decoded_jwt = validator.extract_kid(id_token)
31
32
  public_key_manager.refresh_publickeys!
@@ -36,8 +37,7 @@ module FirebaseTokenAuth
36
37
  end
37
38
 
38
39
  def create_custom_token(uid, additional_claims = nil)
39
- # TODO: implement Error
40
- raise unless configuration.configured_for_custom_token?
40
+ raise ConfigurationError, 'To create custom token, You must configure credentials via json or environmental variables.' unless configuration.configured_for_custom_token?
41
41
 
42
42
  now_seconds = Time.now.to_i
43
43
  payload = { iss: configuration.client_email,
@@ -24,8 +24,7 @@ module FirebaseTokenAuth
24
24
  end
25
25
 
26
26
  def prepare
27
- # TODO: implement error
28
- raise unless project_id
27
+ raise ConfigurationError, 'project_id is required to use firebase_token_auth gem.' unless project_id
29
28
  return unless configured_for_custom_token?
30
29
 
31
30
  @auth = if json_key_io
@@ -0,0 +1,104 @@
1
+ module FirebaseTokenAuth
2
+ class APIError < StandardError; end
3
+ class NetworkError < APIError; end
4
+
5
+ class HttpError < APIError
6
+ attr_reader :response
7
+
8
+ def initialize(message, response)
9
+ super(message)
10
+ @response = response
11
+ end
12
+ end
13
+
14
+ class ClientError < HttpError; end
15
+
16
+ class BadRequest < ClientError; end # status: 400
17
+ class Unauthorized < ClientError; end # status: 401
18
+ class PaymentRequired < ClientError; end # status: 402
19
+ class Forbidden < ClientError; end # status: 403
20
+ class NotFound < ClientError; end # status: 404
21
+ class MethodNotAllowed < ClientError; end # status: 405
22
+ class NotAcceptable < ClientError; end # status: 406
23
+ class ProxyAuthenticationRequired < ClientError; end # status: 407
24
+ class RequestTimeout < ClientError; end # status: 408
25
+ class Conflict < ClientError; end # status: 409
26
+ class Gone < ClientError; end # status: 410
27
+ class LengthRequired < ClientError; end # status: 411
28
+ class PreconditionFailed < ClientError; end # status: 412
29
+ class PayloadTooLarge < ClientError; end # status: 413
30
+ class URITooLong < ClientError; end # status: 414
31
+ class UnsupportedMediaType < ClientError; end # status: 415
32
+ class RangeNotSatisfiable < ClientError; end # status: 416
33
+ class ExpectationFailed < ClientError; end # status: 417
34
+ class ImaTeapot < ClientError; end # status: 418
35
+ class MisdirectedRequest < ClientError; end # status: 421
36
+ class UnprocessableEntity < ClientError; end # status: 422
37
+ class Locked < ClientError; end # status: 423
38
+ class FailedDependency < ClientError; end # status: 424
39
+ class UpgradeRequired < ClientError; end # status: 426
40
+ class PreconditionRequired < ClientError; end # status: 428
41
+ class TooManyRequests < ClientError; end # status: 429
42
+ class RequestHeaderFieldsTooLarge < ClientError; end # status: 431
43
+ class UnavailableForLegalReasons < ClientError; end # status: 451
44
+
45
+ class ServerError < HttpError; end
46
+
47
+ class InternalServerError < ServerError; end # status: 500
48
+ class NotImplemented < ServerError; end # status: 501
49
+ class BadGateway < ServerError; end # status: 502
50
+ class ServiceUnavailable < ServerError; end # status: 503
51
+ class GatewayTimeout < ServerError; end # status: 504
52
+ class HTTPVersionNotSupported < ServerError; end # status: 505
53
+ class VariantAlsoNegotiates < ServerError; end # status: 506
54
+ class InsufficientStorage < ServerError; end # status: 507
55
+ class LoopDetected < ServerError; end # status: 508
56
+ class NotExtended < ServerError; end # status: 510
57
+ class NetworkAuthenticationRequired < ServerError; end # status: 511
58
+
59
+ STATUS_TO_EXCEPTION_MAPPING = {
60
+ '400' => BadRequest,
61
+ '401' => Unauthorized,
62
+ '402' => PaymentRequired,
63
+ '403' => Forbidden,
64
+ '404' => NotFound,
65
+ '405' => MethodNotAllowed,
66
+ '406' => NotAcceptable,
67
+ '407' => ProxyAuthenticationRequired,
68
+ '408' => RequestTimeout,
69
+ '409' => Conflict,
70
+ '410' => Gone,
71
+ '411' => LengthRequired,
72
+ '412' => PreconditionFailed,
73
+ '413' => PayloadTooLarge,
74
+ '414' => URITooLong,
75
+ '415' => UnsupportedMediaType,
76
+ '416' => RangeNotSatisfiable,
77
+ '417' => ExpectationFailed,
78
+ '418' => ImaTeapot,
79
+ '421' => MisdirectedRequest,
80
+ '422' => UnprocessableEntity,
81
+ '423' => Locked,
82
+ '424' => FailedDependency,
83
+ '426' => UpgradeRequired,
84
+ '428' => PreconditionRequired,
85
+ '429' => TooManyRequests,
86
+ '431' => RequestHeaderFieldsTooLarge,
87
+ '451' => UnavailableForLegalReasons,
88
+ '500' => InternalServerError,
89
+ '501' => NotImplemented,
90
+ '502' => BadGateway,
91
+ '503' => ServiceUnavailable,
92
+ '504' => GatewayTimeout,
93
+ '505' => HTTPVersionNotSupported,
94
+ '506' => VariantAlsoNegotiates,
95
+ '507' => InsufficientStorage,
96
+ '508' => LoopDetected,
97
+ '510' => NotExtended,
98
+ '511' => NetworkAuthenticationRequired
99
+ }.freeze
100
+
101
+ class ValidationError < StandardError; end
102
+ class ConfigurationError < StandardError; end
103
+ class ArgumentError < StandardError; end
104
+ end
@@ -19,7 +19,7 @@ module FirebaseTokenAuth
19
19
  private
20
20
 
21
21
  def fetch_publickeys_hash
22
- res = Net::HTTP.get_response(URI(PUBLIC_KEY_URL))
22
+ res = exception_handler(Net::HTTP.get_response(URI(PUBLIC_KEY_URL)))
23
23
  @public_keys = JSON.parse(res.body).transform_values! { |v| OpenSSL::X509::Certificate.new(v) }
24
24
  @expire_time = cache_control_header_to_expire_time(res['Cache-Control'])
25
25
  end
@@ -31,5 +31,12 @@ module FirebaseTokenAuth
31
31
  def cache_control_header_to_expire_time(cache_control_header)
32
32
  Time.now.to_i + cache_control_header.match(/max-age=([0-9]*)/)[1].to_i
33
33
  end
34
+
35
+ def exception_handler(response)
36
+ error = STATUS_TO_EXCEPTION_MAPPING[response.code]
37
+ raise error.new("Receieved an error response #{response.code} #{error.to_s.split('::').last}: #{response.body}", response) if error
38
+
39
+ response
40
+ end
34
41
  end
35
42
  end
@@ -7,13 +7,13 @@ module FirebaseTokenAuth
7
7
  payload = decoded_jwt[0]
8
8
  header = decoded_jwt[1]
9
9
  issuer = ISSUER_BASE_URL + project_id
10
- raise unless header['kid']
11
- raise unless header['alg'] == ALGORITHM
12
- raise unless payload['aud'] == project_id
13
- raise unless payload['iss'] == issuer
14
- raise unless payload['sub'].is_a?(String)
15
- raise if payload['sub'].empty?
16
- raise if payload['sub'].size > 128
10
+ raise ValidationError, 'Firebase ID token has no "kid" claim.' unless header['kid']
11
+ raise ValidationError, "Firebase ID token has incorrect algorithm. Expected \"#{ALGORITHM}\" but got \"#{header['alg']}\"." unless header['alg'] == ALGORITHM
12
+ raise ValidationError, "Firebase ID token has incorrect \"aud\" (audience) claim. Expected \"#{project_id}\" but got \"#{payload['aud']}\"." unless payload['aud'] == project_id
13
+ raise ValidationError, "Firebase ID token has \"iss\" (issuer) claim. Expected \"#{issuer}\" but got \"#{payload['iss']}\"." unless payload['iss'] == issuer
14
+ raise ValidationError, 'Firebase ID token has no "sub" (subject) claim.' unless payload['sub'].is_a?(String)
15
+ raise ValidationError, 'Firebase ID token has an empty string "sub" (subject) claim.' if payload['sub'].empty?
16
+ raise ValidationError, 'Firebase ID token has "sub" (subject) claim longer than 128 characters.' if payload['sub'].size > 128
17
17
  end
18
18
 
19
19
  def extract_kid(id_token)
@@ -1,3 +1,3 @@
1
1
  module FirebaseTokenAuth
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firebase_token_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - miyataka
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-07-08 00:00:00.000000000 Z
11
+ date: 2020-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-api-client
@@ -61,6 +61,7 @@ files:
61
61
  - lib/firebase_token_auth/admin_client.rb
62
62
  - lib/firebase_token_auth/client.rb
63
63
  - lib/firebase_token_auth/configuration.rb
64
+ - lib/firebase_token_auth/exceptions.rb
64
65
  - lib/firebase_token_auth/public_key_manager.rb
65
66
  - lib/firebase_token_auth/validator.rb
66
67
  - lib/firebase_token_auth/version.rb
@@ -85,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
86
  - !ruby/object:Gem::Version
86
87
  version: '0'
87
88
  requirements: []
88
- rubygems_version: 3.1.2
89
+ rubygems_version: 3.0.3
89
90
  signing_key:
90
91
  specification_version: 4
91
92
  summary: Firebase Authentication API wrapper for serverside. It support custom token