Class: FirebaseApiService

Inherits:
Object
  • Object
show all
Defined in:
app/services/firebase_api_service.rb

Constant Summary collapse

FIREBASE_PROJECT_ID =
Rails.application.credentials.firebase_project_id

Class Method Summary collapse

Class Method Details

.access_tokenObject



219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/services/firebase_api_service.rb', line 219

def access_token
  @access_token ||= begin
    scopes = [
      'https://www.googleapis.com/auth/identitytoolkit'
    ]
    authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
      json_key_io: StringIO.new(Rails.application.credentials.),
      scope: scopes
    )
    authorizer.fetch_access_token!['access_token']
  end
end

.connObject



232
233
234
235
236
237
238
239
240
# File 'app/services/firebase_api_service.rb', line 232

def conn
  @conn ||= Faraday.new(url: 'https://identitytoolkit.googleapis.com') do |faraday|
    faraday.request :json
    faraday.response :json
    faraday.response :raise_error
    faraday.headers['Authorization'] = "Bearer #{access_token}"
    faraday.adapter Faraday.default_adapter
  end
end

.create_firebase_user(email) ⇒ Object



169
170
171
172
# File 'app/services/firebase_api_service.rb', line 169

def create_firebase_user(email)
  response = conn.post('/v1/accounts:signUp', { email: email })
  response.body
end

.create_password_and_verify_email(oob_code, new_password) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'app/services/firebase_api_service.rb', line 109

def create_password_and_verify_email(oob_code, new_password)
  purpose = Auth::Token::PURPOSES[:create_password_and_verify_email]

  user = verify_oob_code!(oob_code, purpose)
  email = user.email

  reset_password_on_firebase!(email, new_password)
  verify_email_on_firebase!(email)

  RevokedToken.create!(token: oob_code, erp_user_id: user.id, reason: RevokedToken::REASONS[:USED])

  user
end


192
193
194
195
196
197
198
199
# File 'app/services/firebase_api_service.rb', line 192

def create_password_and_verify_email_link(user:, app_mode:)
  email_link(
    purpose: Auth::Token::PURPOSES[:create_password_and_verify_email],
    user_id: user.id,
    locale: user.locale,
    app_mode: app_mode
  )
end


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/services/firebase_api_service.rb', line 201

def email_link(purpose:, user_id:, app_mode:, locale: 'en')
  oob_code = Auth::Token.encode(
    user_id: user_id,
    purposes: [purpose]
  )

  base_url = "#{app_mode.web_uri}/auth/email_action"
  query_params = {
    purpose: purpose,
    oobCode: oob_code,
    locale: locale
  }

  uri = URI(base_url)
  uri.query = URI.encode_www_form(query_params)
  uri.to_s
end


174
175
176
177
178
179
180
181
# File 'app/services/firebase_api_service.rb', line 174

def email_verification_link(user:, app_mode:)
  email_link(
    purpose: Auth::Token::PURPOSES[:verify_email],
    user_id: user.id,
    locale: user.locale,
    app_mode: app_mode
  )
end

.find_firebase_user(email) ⇒ Object



159
160
161
162
163
164
165
166
167
# File 'app/services/firebase_api_service.rb', line 159

def find_firebase_user(email)
  response = conn.post('/v1/accounts:lookup', { email: email })
  firebase_user_data = response.body.dig('users', 0)
  raise ActiveRecord::RecordNotFound unless firebase_user_data

  firebase_user_data
rescue ActiveRecord::RecordNotFound
  nil
end

.find_or_register_user(credential, app_mode) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
# File 'app/services/firebase_api_service.rb', line 33

def find_or_register_user(credential, app_mode)
  id_token = credential['_tokenResponse']['idToken']
  user_data = verify_id_token(id_token: id_token)
  email = user_data[:email]

  ErpUser.find_by(email: email) || UserRegistrationService.new(
    email: email,
    navigation_configuration_id: app_mode.user_navigation_configuration_id,
    permission_groups_codes: app_mode.permission_groups_codes
  ).register
end

.firebase_user(email) ⇒ Object



155
156
157
# File 'app/services/firebase_api_service.rb', line 155

def firebase_user(email)
  find_firebase_user(email) || create_firebase_user(email)
end

.firebase_user_verified?(email) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
# File 'app/services/firebase_api_service.rb', line 123

def firebase_user_verified?(email)
  firebase_user(email)['emailVerified']
end


183
184
185
186
187
188
189
190
# File 'app/services/firebase_api_service.rb', line 183

def password_reset_link(user:, app_mode:)
  email_link(
    purpose: Auth::Token::PURPOSES[:reset_password],
    user_id: user.id,
    locale: user.locale,
    app_mode: app_mode
  )
end

.reset_password(oob_code, new_password) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/services/firebase_api_service.rb', line 96

def reset_password(oob_code, new_password)
  purpose = Auth::Token::PURPOSES[:reset_password]

  user = verify_oob_code!(oob_code, purpose)
  email = user.email

  reset_password_on_firebase!(email, new_password)

  RevokedToken.create!(token: oob_code, erp_user_id: user.id, reason: RevokedToken::REASONS[:USED])

  user
end

.reset_password_on_firebase!(email, password) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/services/firebase_api_service.rb', line 139

def reset_password_on_firebase!(email, password)
  response = firebase_user(email)
  remote_id = response['localId']

  update_response = conn.post(
    '/v1/accounts:update',
    {
      localId: remote_id,
      password: password,
      emailVerified: true
    }
  )

  update_response.body
end

.send_password_reset_email(email, app_mode) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'app/services/firebase_api_service.rb', line 56

def send_password_reset_email(email, app_mode)
  user = ErpUser.find_by!(email: email)
  link = password_reset_link(user: user, app_mode: app_mode)

  AuthMailer.password_reset(
    user: user,
    link: link,
    app_mode: app_mode
  ).deliver_now
end

.send_verification_email(credential, app_mode) ⇒ Object



45
46
47
48
49
50
51
52
53
54
# File 'app/services/firebase_api_service.rb', line 45

def send_verification_email(credential, app_mode)
  user = find_or_register_user(credential, app_mode)
  link = email_verification_link(user: user, app_mode: app_mode)

  AuthMailer.verification(
    user: user,
    link: link,
    app_mode: app_mode
  ).deliver_now
end

.verify_email(oob_code) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
# File 'app/services/firebase_api_service.rb', line 84

def verify_email(oob_code)
  purpose = Auth::Token::PURPOSES[:verify_email]
  user = verify_oob_code!(oob_code, purpose)
  email = user.email

  verify_email_on_firebase!(email)

  RevokedToken.create!(token: oob_code, erp_user_id: user.id, reason: RevokedToken::REASONS[:USED])

  user
end

.verify_email_on_firebase!(email) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
# File 'app/services/firebase_api_service.rb', line 127

def verify_email_on_firebase!(email)
  remote_id = firebase_user(email)['localId']
  response = conn.post(
    '/v1/accounts:update',
    {
      localId: remote_id,
      emailVerified: true
    }
  )
  response.body['emailVerified']
end

.verify_id_token(id_token:) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'app/services/firebase_api_service.rb', line 5

def verify_id_token(id_token:)
  header = JWT.decode(id_token, nil, false)[1]
  kid = header['kid']
  uri = URI('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')

  public_keys = JSON.parse(Net::HTTP.get(uri))
  public_key = public_keys[kid]

  x509 = OpenSSL::X509::Certificate.new public_key
  result = JWT.decode(
    id_token.strip,
    x509.public_key,
    true,
    {
      algorithm: 'RS256',
      verify_iat: true,
      iss: "https://securetoken.google.com/#{FIREBASE_PROJECT_ID}",
      aud: FIREBASE_PROJECT_ID
    }
  )

  email = result.dig(0, 'firebase', 'identities', 'email', 0)

  {
    email: email
  }
end

.verify_oob_code!(oob_code, purpose) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'app/services/firebase_api_service.rb', line 67

def verify_oob_code!(oob_code, purpose)
  decoded = Auth::Token.decode(token: oob_code, purposes: [purpose])
  user_id = decoded[:user_id]

  user = ErpUser.find(user_id)

  if RevokedToken.exists?(token: oob_code, erp_user_id: user_id, reason: RevokedToken::REASONS[:USED])
    raise 'OOB code already used'
  end

  if purpose == Auth::Token::PURPOSES[:verify_email] && firebase_user_verified?(user.email)
    raise 'User already verified'
  end

  user
end