firebase_token_auth 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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