firebase-authentication 0.2.0 → 0.3.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: 26d45db46a0e99567b9f9b67c0eb991327cc99a4dc58ff978161b8f293620885
4
- data.tar.gz: 6396f770c206014c435df545bde268e2f9199d1fb6a3388d8496828be0cb7d7e
3
+ metadata.gz: 52f63a09db5325c16fef42a13e248799767c1b9378af29a029e4ed5e7532b004
4
+ data.tar.gz: da4d219fc4d98dabf25e86ec4944188dc8e45f00bb9182fb2dabd03866db797a
5
5
  SHA512:
6
- metadata.gz: 51b81400b334dbdc648bf76d3e2bb1ddbedd2d694d98b8fa7b99af2001e3c82318edb7798f0557c85bd51a82a33189432b72ddb2c56555ee7bc95402e97ddd42
7
- data.tar.gz: '042855574e77b4a107a3fd03020502b6846e1e5d9b76052a657fbc0bf3a99b045376ff0d3bce726b2ecacd263aadd05484daa7c1129fc3b8e232ea3d7282c9a1'
6
+ metadata.gz: 7051a16c7a466845d33617a9cf0b9ea1f9413d66fa0da1eb57d8a574e9d4360838ad4e976484862f9f377fcfdb761047a657236e7e66badc4545ba7b1fbc6c88
7
+ data.tar.gz: b4f2ba99ba8758e6e056df2f42787e211e230d25aedde1a76548b852e2ba336416c74bd9ae42e26c0eb92e73243d6b301c5c8fb2d478139e11e68243fc95d3e0
data/.rubocop.yml CHANGED
@@ -33,6 +33,9 @@ Layout/LineLength:
33
33
  Exclude:
34
34
  - "lib/firebase/**/*"
35
35
 
36
+ Metrics/AbcSize:
37
+ Max: 30
38
+
36
39
  Metrics/MethodLength:
37
40
  Max: 20
38
41
  Exclude:
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in firebase_auth_for_ruby.gemspec
4
4
  gemspec
5
5
 
6
+ gem "dotenv-rails"
6
7
  gem "jwt"
7
8
  gem "rails", "~> 6.1.4"
8
9
  gem "rake"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- firebase-authentication (0.1.0)
4
+ firebase-authentication (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -70,6 +70,10 @@ GEM
70
70
  concurrent-ruby (1.1.9)
71
71
  crass (1.0.6)
72
72
  diff-lcs (1.4.4)
73
+ dotenv (2.7.6)
74
+ dotenv-rails (2.7.6)
75
+ dotenv (= 2.7.6)
76
+ railties (>= 3.2)
73
77
  erubi (1.10.0)
74
78
  globalid (0.5.2)
75
79
  activesupport (>= 5.0)
@@ -172,6 +176,7 @@ PLATFORMS
172
176
  ruby
173
177
 
174
178
  DEPENDENCIES
179
+ dotenv-rails
175
180
  firebase-authentication!
176
181
  jwt
177
182
  rails (~> 6.1.4)
@@ -1,5 +1,5 @@
1
1
  module Firebase
2
2
  module Authentication
3
- VERSION = "0.2.0".freeze
3
+ VERSION = "0.3.0".freeze
4
4
  end
5
5
  end
@@ -1,10 +1,123 @@
1
1
  require_relative "authentication/version"
2
- require_relative "authentication/config"
3
2
  require_relative "authentication/service"
4
3
 
5
4
  module Firebase
6
5
  module Authentication
7
- class Error < StandardError; end
8
- # Your code goes here...
6
+ ALGORITHM = "RS256".freeze
7
+ ISSUER_BASE_URL = "https://securetoken.google.com/".freeze
8
+ CLIENT_CERT_URL = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com".freeze
9
+
10
+ class << self
11
+ def verify(token)
12
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
13
+ Rails.logger.info token
14
+ raise "id token must be a String" unless token.is_a?(String)
15
+
16
+ full_decoded_token = _decode_token(token)
17
+
18
+ err_msg = _validate_jwt(full_decoded_token)
19
+ raise err_msg if err_msg
20
+
21
+ public_key = _fetch_public_keys[full_decoded_token[:header]["kid"]]
22
+ unless public_key
23
+ raise 'Firebase ID token has "kid" claim which does not correspond to a known public key.'\
24
+ "Most likely the ID token is expired, so get a fresh token from your client app and try again."
25
+ end
26
+
27
+ certificate = OpenSSL::X509::Certificate.new(public_key)
28
+ decoded_token = _decode_token(token, certificate.public_key, verify: true, options: { algorithm: ALGORITHM, verify_iat: true })
29
+
30
+ {
31
+ "uid" => decoded_token[:payload]["sub"],
32
+ "decoded_token" => decoded_token
33
+ }
34
+ end
35
+
36
+ def create_custom_token(uid, claims = {})
37
+ private_key = OpenSSL::PKey::RSA.new Global.firebase.private_key.gsub('\\n', "\n")
38
+ service_account_email = Global.firebase.client_email
39
+ now_seconds = Time.now.to_i
40
+ payload = { iss: service_account_email,
41
+ sub: service_account_email,
42
+ aud: "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
43
+ iat: now_seconds,
44
+ exp: now_seconds + (60 * 60),
45
+ uid: uid,
46
+ claims: claims }
47
+ JWT.encode payload, private_key, "RS256"
48
+ end
49
+
50
+ private
51
+
52
+ def _decode_token(token, key = nil, verify: false, options: {})
53
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
54
+ begin
55
+ decoded_token = JWT.decode(token, key, verify, options)
56
+ rescue JWT::ExpiredSignature => e
57
+ raise "Firebase ID token has expired. Get a fresh token from your client app and try again. #{e.message}"
58
+ rescue JWT::InvalidAudError, JWT::DecodeError, JWT::VerificationError => e
59
+ raise "Firebase JWT Error. #{e.message}"
60
+ rescue StandardError => e
61
+ raise "Firebase ID token has invalid signature. #{e.message}"
62
+ end
63
+
64
+ {
65
+ payload: decoded_token[0],
66
+ header: decoded_token[1]
67
+ }
68
+ end
69
+
70
+ def _fetch_public_keys
71
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
72
+ uri = URI.parse(CLIENT_CERT_URL)
73
+ https = Net::HTTP.new(uri.host, uri.port)
74
+ https.use_ssl = true
75
+
76
+ res = https.start do
77
+ https.get(uri.request_uri)
78
+ end
79
+ data = JSON.parse(res.body)
80
+
81
+ if data["error"]
82
+ msg = "Error fetching public keys for Google certs: #{data["error"]}"
83
+ msg += " (#{res["error_description"]})" if data["error_description"]
84
+
85
+ raise msg
86
+ end
87
+
88
+ data
89
+ end
90
+
91
+ def _validate_jwt(json)
92
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
93
+ error = _validate_jwt_header(json[:header])
94
+ error || _validate_jwt_payload(json[:payload])
95
+ end
96
+
97
+ def _validate_jwt_header(header)
98
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
99
+ return 'Firebase ID token has no "kid" claim.' unless header["kid"]
100
+
101
+ return "Firebase ID token has incorrect algorithm. Expected \"#{ALGORITHM}\" but got \"#{header["alg"]}\"."\
102
+ unless header["alg"] == ALGORITHM
103
+ end
104
+
105
+ def _validate_jwt_payload(payload)
106
+ Rails.logger.info "#{self.class.name}\##{__method__} called."
107
+ project_id = ENV.fetch("FIREBASE_PROJECT_ID")
108
+ unless payload["aud"] == project_id
109
+ return "Firebase ID token has incorrect \'aud\' (audience) claim. Expected \"#{project_id}\" but got \"#{payload["aud"]}\"."
110
+ end
111
+
112
+ issuer = ISSUER_BASE_URL + project_id
113
+ unless payload["iss"] == issuer
114
+ return "Firebase ID token has incorrect \'iss\' (issuer) claim. Expected \"#{issuer}\" but got \"#{payload["iss"]}\"."
115
+ end
116
+
117
+ return 'Firebase ID token has no "sub" (subject) claim.' unless payload["sub"].is_a?(String)
118
+ return 'Firebase ID token has an empty string "sub" (subject) claim.' if payload["sub"].empty?
119
+ return 'Firebase ID token has "sub" (subject) claim longer than 128 characters.' if payload["sub"].size > 128
120
+ end
121
+ end
9
122
  end
10
123
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firebase-authentication
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - shuntagami
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-03 00:00:00.000000000 Z
11
+ date: 2021-10-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: