standard_id-google 0.5.0 → 0.7.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: f22ca920dcb306788b3588bf99ac704b9ce5e40cbb9c423a551e064ab6b236a9
4
- data.tar.gz: f8492f9d9af4dbf74af4062524e0c217e20e50ec3420605a76d363154cacc2f5
3
+ metadata.gz: 40c4313fc9b3b04605a0a36353aedfbec7cfe109f1fdd3471455c28a2c908fc8
4
+ data.tar.gz: 21cf43e45c1f071ad0aec27c322c42e6ff719494da7941ee7f24b9f6a80d8cae
5
5
  SHA512:
6
- metadata.gz: 9b8d7e1bb7bdfa6302108521bf603a8727a61cd2a6893dcfd8ed230f9999697aa627f912b870528833f9b4781cbe1dbd868f761bdec013ae6cb7ff3a2f8af004
7
- data.tar.gz: 53b44b47d85d30da76de8dbdf1f55953c3b3f4a31fb3808c769b8a72175210d8ac0fdbbbbbe0d52eb7e45817666d17edd5ce2947b1dfc16462d3f12d92226544
6
+ metadata.gz: 7630091992ad0beaf567eee9e89acdcdf73625b85119a0e5ace6e209b4301f974dfe69d4efa3b679e7db7affcb93bf7028940c4f21c51e526cad8c87a58b618c
7
+ data.tar.gz: 29a6a81e3224960ec686fd3e700a49ea1868e4ea19643ec5e1cfa3986c4c1b80f5e407bcdedd2bdbe8839b723f0ab5e49700fbbfb173e8d157777417bb355c1f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-10-06
11
+
12
+ ### Added
13
+
14
+ - **Pin sign-in to Google Workspace domains: `social.google_hosted_domains`.**
15
+ An array of domains (default empty: nothing changes). While set, every
16
+ Google login, on the web code flow and the native `id_token` flow alike,
17
+ must present a verified ID token whose `hd` claim equals one of them
18
+ (case-insensitive, exact: no subdomains), with `email_verified` true and an
19
+ email whose domain is that `hd`. Anything else, including consumer gmail
20
+ (which has no `hd`), is refused with `StandardId::InvalidRequestError`
21
+ before any account is touched. In the web flow the ID token is required and
22
+ decides identity; the access-token-only sign-in is refused while pinned
23
+ (it has no ID token to pin on). With exactly one domain, `hd` is also sent
24
+ as the account-chooser hint (a caller-supplied `hd` still wins).
25
+ **Security note:** pinning is enforced server-side on the verified ID
26
+ token; the `hd` URL parameter is only a hint and proves nothing.
27
+ - **`trusted_for_linking?` is true while pinned** (standard_id >= 0.46), so a
28
+ Workspace login may link to the organisation's existing account for the same
29
+ address under `link_strategy: :strict`. Unpinned it stays false.
30
+ - **`StandardId::Google.staff_policy`** (standard_id >= 0.45): a
31
+ `login_method_policy` that lets staff in only via Google with a pinned
32
+ domain, mirroring `StandardId::VoidWhichBinds.staff_policy` (`staff_predicate:`,
33
+ `fallback:`, `social.google_require_for_staff`, `social.google_staff_predicate`).
34
+ `StandardId::Google.any_of(*policies)` combines it with another staff policy.
35
+ - `verify_id_token` now also returns the `hd` claim.
36
+
37
+ ## [0.6.0] - 2026-10-02
38
+
39
+ Pairs with **standard_id 0.44.0** (security fix L1-01), which matches returning
40
+ social logins on the provider's `sub` and links an existing account only when
41
+ the provider reports `email_verified`.
42
+
43
+ ### Fixed
44
+
45
+ - **The code-exchange and access-token paths now return `sub` and
46
+ `email_verified`.** They read Google's OAuth2 v2 userinfo endpoint, which
47
+ calls those claims `id` and `verified_email`, and returned its JSON as-is, so
48
+ standard_id never saw a subject for web (code) or native access-token
49
+ sign-ins. `fetch_user_info` now adds `sub` (from `id`) and `email_verified`
50
+ (from `verified_email`), keeping the original keys, and raises
51
+ `StandardId::InvalidRequestError` if the userinfo subject differs from the
52
+ `sub` tokeninfo reported for the access token. The ID-token path already
53
+ returned both claims (`email_verified` arrives there as the string `"true"`,
54
+ which standard_id 0.44 accepts).
55
+
56
+ ### Upgrade
57
+
58
+ - Bump together with standard_id 0.44.0. On standard_id < 0.44 the extra keys
59
+ are ignored, so this release is also safe on its own.
60
+
61
+ ### Changed
62
+
63
+ - **Requires Rails 8.1** (`activesupport >= 8.1`, was `>= 8.0`). Every
64
+ consumer app already runs 8.1; 8.0 was never exercised in CI.
65
+
10
66
  ## [0.5.0] - 2026-09-24
11
67
 
12
68
  Adopts the provider-plugin API of standard_id 0.42.
data/README.md CHANGED
@@ -52,6 +52,59 @@ end
52
52
 
53
53
  With those values in place, StandardId routes such as `/auth/callback/google` continue to function using this provider gem.
54
54
 
55
+ ### Pin sign-in to a Google Workspace domain
56
+
57
+ To make Google the organisation's own identity provider, pin it to the
58
+ Workspace domain(s) your admins control:
59
+
60
+ ```ruby
61
+ StandardId.configure do |config|
62
+ config.social.google_hosted_domains = ["example.com"]
63
+ end
64
+ ```
65
+
66
+ With the default (empty), nothing changes. While set, every Google login (web
67
+ and native `id_token`) is refused unless the verified ID token has an `hd`
68
+ claim exactly matching one of the domains (case-insensitive; no subdomains),
69
+ `email_verified` is true, and the email's domain equals `hd`. Consumer Google
70
+ accounts have no `hd`, so they are refused. The refusal is a
71
+ `StandardId::InvalidRequestError` raised before any account is created or
72
+ linked. While pinned the web flow requires the ID token and takes identity from
73
+ it, and access-token-only sign-in is refused. With exactly one domain, `hd` is
74
+ also sent as the account-chooser hint (pass your own `hd:` to override).
75
+
76
+ **Security:** pinning is enforced server-side on the verified ID token, never on
77
+ the `hd` URL parameter, which a user can edit away. Only trust it if the
78
+ domains' Workspace admins are the people who may create addresses on them.
79
+
80
+ **Linking.** While pinned, `trusted_for_linking?` is `true` (standard_id 0.46),
81
+ so under `link_strategy: :strict` a Workspace login may link to an existing
82
+ account with the same verified email. Core asks per provider class, not per
83
+ login, which is safe here because core calls it only for a login whose
84
+ `get_user_info` has just passed the pin; unpinned it is `false`, as for any
85
+ public IdP.
86
+
87
+ **Staff policy.** To require staff to use it (standard_id 0.45+):
88
+
89
+ ```ruby
90
+ StandardId.configure do |config|
91
+ config.login_method_policy = StandardId::Google.staff_policy(
92
+ staff_predicate: ->(account) { account.staff? }
93
+ )
94
+ end
95
+ ```
96
+
97
+ Staff may sign in only with `auth_method: :social`, provider `google`, and only
98
+ while `google_hosted_domains` is set; password, passwordless and remember-me
99
+ (and refreshes of those) are refused, as in the void_which_binds policy.
100
+ Non-staff are allowed everything, or go to `fallback:`. Without a predicate
101
+ the policy raises (fails closed); `social.google_require_for_staff = false`
102
+ switches it off. A host running both this and
103
+ `StandardId::VoidWhichBinds.staff_policy` should combine them with
104
+ `StandardId::Google.any_of(google_policy, void_which_binds_policy)`: staff are
105
+ admitted by either method and refused otherwise (chaining with `fallback:`
106
+ would lock staff out of the other method).
107
+
55
108
  ### ENV fallback
56
109
 
57
110
  On `standard_id` 0.42+, a field you never assign falls back to the ENV
@@ -28,4 +28,8 @@
28
28
  StandardId.configure do |config|
29
29
  config.social.google_client_id = ENV.fetch("GOOGLE_CLIENT_ID", nil)
30
30
  config.social.google_client_secret = ENV.fetch("GOOGLE_CLIENT_SECRET", nil)
31
+
32
+ # Optional: pin sign-in to your Google Workspace domain(s). Enforced on the
33
+ # verified ID token's `hd` claim (consumer gmail is refused); see the README.
34
+ # config.social.google_hosted_domains = ["example.com"]
31
35
  end
@@ -32,16 +32,48 @@ module StandardId
32
32
  "google"
33
33
  end
34
34
 
35
+ # The Google Workspace domains sign-in is pinned to
36
+ # (`social.google_hosted_domains`): lowercase, stripped, de-duplicated.
37
+ # Empty means the provider is not pinned and behaves as it always has.
38
+ def hosted_domains
39
+ raw = StandardId.config.google_hosted_domains
40
+ list = raw.is_a?(String) ? raw.split(/[\s,]+/) : Array(raw)
41
+ list.map { |domain| domain.to_s.strip.downcase }.reject(&:empty?).uniq
42
+ end
43
+
44
+ def pinned?
45
+ hosted_domains.any?
46
+ end
47
+
48
+ # True ONLY while sign-in is pinned to Workspace domains. Core calls
49
+ # this on the class, after get_user_info has returned for the very
50
+ # login being linked, so there is no per-login question to answer:
51
+ # while pinned, every login that gets that far presented a verified
52
+ # ID token whose `hd` is one of the configured domains and whose
53
+ # email is on that domain (see enforce_hosted_domain!), so the
54
+ # address is one the domain's Workspace admin provisioned. Unpinned,
55
+ # Google is a public IdP and must never be trusted for linking.
56
+ def trusted_for_linking?
57
+ pinned?
58
+ end
59
+
35
60
  def supported_authorization_params
36
61
  [:nonce, :login_hint, :prompt, :scope, :access_type, :hd, :response_mode, :include_granted_scopes]
37
62
  end
38
63
 
39
64
  def authorization_url(state:, redirect_uri:, **options)
65
+ # With exactly one pinned domain, `hd` is sent as Google's account
66
+ # chooser hint (a caller-supplied `hd` still wins). It is only a
67
+ # hint: pinning is enforced on the verified ID token, never on it.
68
+ defaults = AUTHORIZATION_PARAM_DEFAULTS
69
+ domains = hosted_domains
70
+ defaults = defaults.merge(hd: domains.first) if domains.one?
71
+
40
72
  build_authorization_url(
41
73
  endpoint: AUTH_ENDPOINT,
42
74
  client_id: credentials[:client_id],
43
75
  redirect_uri:, state:, options:,
44
- defaults: AUTHORIZATION_PARAM_DEFAULTS
76
+ defaults: defaults
45
77
  )
46
78
  end
47
79
 
@@ -52,6 +84,13 @@ module StandardId
52
84
  tokens: { id_token: id_token }
53
85
  )
54
86
  elsif access_token.present?
87
+ # An access token carries no ID token, so there is no verified
88
+ # `hd` claim to pin on: refused rather than trusted.
89
+ if pinned?
90
+ raise StandardId::InvalidRequestError,
91
+ "Google access-token sign-in is not available while google_hosted_domains is set"
92
+ end
93
+
55
94
  build_response(
56
95
  fetch_user_info(access_token: access_token),
57
96
  tokens: { access_token: access_token }
@@ -77,7 +116,12 @@ module StandardId
77
116
  def config_schema
78
117
  {
79
118
  google_client_id: { type: :string, default: -> { legacy_env(:google_client_id) } },
80
- google_client_secret: { type: :string, default: -> { legacy_env(:google_client_secret) }, required: true }
119
+ google_client_secret: { type: :string, default: -> { legacy_env(:google_client_secret) }, required: true },
120
+ # Workspace domains sign-in is pinned to; empty (default) = not pinned.
121
+ google_hosted_domains: { type: :any, default: -> { [] }, env: false },
122
+ # StandardId::Google.staff_policy settings.
123
+ google_require_for_staff: { type: :boolean, default: true },
124
+ google_staff_predicate: { type: :any, default: nil, env: false }
81
125
  }
82
126
  end
83
127
 
@@ -111,6 +155,18 @@ module StandardId
111
155
  access_token = parsed_token["access_token"]
112
156
  raise StandardId::InvalidRequestError, "Google token response is missing access_token" if access_token.blank?
113
157
 
158
+ if pinned?
159
+ # Pinned: the ID token is required and decides identity; the
160
+ # userinfo only adds profile fields.
161
+ if parsed_token["id_token"].blank?
162
+ raise StandardId::InvalidRequestError, "Google token response is missing id_token"
163
+ end
164
+
165
+ id_claims = verify_id_token(id_token: parsed_token["id_token"], nonce: nonce)
166
+ user_info = pinned_user_info(fetch_user_info(access_token: access_token), id_claims)
167
+ return build_response(user_info, tokens: extract_tokens(parsed_token))
168
+ end
169
+
114
170
  # Web flow with a server-generated nonce: check it on the ID token.
115
171
  if parsed_token["id_token"].present? && nonce.present?
116
172
  verify_id_token(id_token: parsed_token["id_token"], nonce: nonce)
@@ -143,7 +199,10 @@ module StandardId
143
199
  # Constant-time, and the error never echoes either value.
144
200
  verify_nonce!(expected: nonce, actual: token_info["nonce"])
145
201
 
202
+ enforce_hosted_domain!(token_info) if pinned?
203
+
146
204
  {
205
+ "hd" => token_info["hd"],
147
206
  "sub" => token_info["sub"],
148
207
  "email" => token_info["email"],
149
208
  "email_verified" => token_info["email_verified"],
@@ -160,19 +219,52 @@ module StandardId
160
219
  rescue_to_oauth_error do
161
220
  raise StandardId::InvalidRequestError, "Google access token is missing" if access_token.blank?
162
221
 
163
- verify_token(access_token)
222
+ token_info = verify_token(access_token)
164
223
  user_response = HttpClient.get_with_bearer(USERINFO_ENDPOINT, access_token)
165
224
 
166
225
  unless user_response.is_a?(Net::HTTPSuccess)
167
226
  raise StandardId::InvalidRequestError, "Failed to fetch Google user info: HTTP #{user_response.code}"
168
227
  end
169
228
 
170
- JSON.parse(user_response.body)
229
+ with_oidc_claims(JSON.parse(user_response.body), token_info)
171
230
  end
172
231
  end
173
232
 
174
233
  private
175
234
 
235
+ # Pins sign-in to the configured Workspace domains, on the claims of
236
+ # a verified ID token: `hd` must be one of them (an exact,
237
+ # case-insensitive match; consumer accounts have no `hd`), the email
238
+ # must be verified, and its domain must equal `hd`. Messages never
239
+ # echo the claims.
240
+ def enforce_hosted_domain!(claims)
241
+ hd = claims["hd"]
242
+ hd = hd.is_a?(String) ? hd.strip.downcase : nil
243
+ unless hd.present? && hosted_domains.include?(hd)
244
+ raise StandardId::InvalidRequestError, "Google account is not in an allowed Workspace domain"
245
+ end
246
+
247
+ unless [true, "true"].include?(claims["email_verified"])
248
+ raise StandardId::InvalidRequestError, "Google account email is not verified"
249
+ end
250
+
251
+ email = claims["email"]
252
+ local, separator, domain = email.is_a?(String) ? email.strip.rpartition("@") : []
253
+ unless separator == "@" && local.present? && !local.include?("@") && domain.downcase == hd
254
+ raise StandardId::InvalidRequestError, "Google account email is not on its Workspace domain"
255
+ end
256
+ end
257
+
258
+ # Web flow, pinned: identity comes from the verified ID token. The
259
+ # userinfo must agree on the subject, and its profile fields are kept.
260
+ def pinned_user_info(userinfo, id_claims)
261
+ unless userinfo.is_a?(Hash) && userinfo["sub"].to_s == id_claims["sub"].to_s && id_claims["sub"].present?
262
+ raise StandardId::InvalidRequestError, "Google user info does not match the ID token"
263
+ end
264
+
265
+ userinfo.merge(id_claims.slice("sub", "email", "email_verified", "hd"))
266
+ end
267
+
176
268
  # The client ID every flow needs, and the secret only the code
177
269
  # exchange needs (checked there). Raises when the provider is off.
178
270
  def credentials
@@ -200,6 +292,28 @@ module StandardId
200
292
  token_info
201
293
  end
202
294
 
295
+ # The v2 userinfo endpoint names the subject `id` and the verified
296
+ # flag `verified_email`. standard_id (>= 0.44) matches returning
297
+ # logins on the OIDC `sub` and links existing accounts only on
298
+ # `email_verified`, so add both claims; the original keys are kept.
299
+ # The subject must agree with the one tokeninfo reported for the
300
+ # access token, when it reported one.
301
+ def with_oidc_claims(user_info, token_info)
302
+ return user_info unless user_info.is_a?(Hash)
303
+
304
+ claims = user_info.dup
305
+ claims["sub"] = claims["id"].to_s.presence if claims["sub"].blank?
306
+ claims["email_verified"] = claims["verified_email"] if !claims.key?("email_verified") && claims.key?("verified_email")
307
+ claims.compact!
308
+
309
+ token_sub = token_info.is_a?(Hash) ? token_info["sub"].to_s.presence : nil
310
+ if token_sub && claims["sub"] && token_sub != claims["sub"].to_s
311
+ raise StandardId::InvalidRequestError, "Google user info does not match the access token"
312
+ end
313
+
314
+ claims
315
+ end
316
+
203
317
  def error_reason(response)
204
318
  body = JSON.parse(response.body.to_s)
205
319
  reason = body["error"] if body.is_a?(Hash)
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardId
4
+ module Google
5
+ # A `login_method_policy` (standard_id >= 0.45) that refuses every way into
6
+ # a staff account except a Google sign-in pinned to the organisation's
7
+ # Workspace domain(s), so removing someone from the Workspace removes their
8
+ # way into the app:
9
+ #
10
+ # StandardId.configure do |c|
11
+ # c.social.google_hosted_domains = ["example.com"]
12
+ # c.login_method_policy = StandardId::Google.staff_policy(
13
+ # staff_predicate: ->(account) { account.staff? }
14
+ # )
15
+ # end
16
+ #
17
+ # standard_id consults it before any session or token exists, in every flow
18
+ # (web password, passwordless, remember-me, other social providers, the
19
+ # OAuth grants, and each refresh with the original sign-in's method).
20
+ # Remember-me re-authentication is therefore refused for staff, exactly as
21
+ # the void_which_binds policy does: staff sign in again with Google.
22
+ # Non-staff accounts pass through to `fallback`, if given (another policy
23
+ # with the same keyword contract), and are otherwise allowed.
24
+ #
25
+ # Enforced while `social.google_require_for_staff` is true (the default).
26
+ # `staff_predicate` falls back to `social.google_staff_predicate`; with
27
+ # neither, every decision fails closed (raises). A staff Google login is
28
+ # admitted only while `google_hosted_domains` is non-empty: an unpinned
29
+ # Google sign-in proves nothing about the organisation, so it is refused.
30
+ class StaffPolicy
31
+ MESSAGE = "Staff accounts must sign in with the organisation's Google Workspace account"
32
+
33
+ attr_reader :staff_predicate
34
+
35
+ def initialize(staff_predicate: nil, fallback: nil)
36
+ @staff_predicate = staff_predicate
37
+ @fallback = fallback
38
+ end
39
+
40
+ def call(account:, auth_method:, provider:, request: nil, flow: nil)
41
+ if require_for_staff? && staff?(account)
42
+ raise StandardId::LoginMethodDenied, MESSAGE unless pinned_google?(auth_method, provider)
43
+
44
+ return true
45
+ end
46
+ return true if @fallback.nil?
47
+
48
+ context = { account:, auth_method:, provider:, request:, flow: }
49
+ @fallback.call(**StandardId::Utils::CallableParameterFilter.filter(@fallback, context))
50
+ end
51
+
52
+ def staff?(account)
53
+ predicate = @staff_predicate || StandardId.config.google_staff_predicate
54
+ unless predicate.respond_to?(:call)
55
+ raise StandardId::ConfigurationError, "StandardId::Google.staff_policy needs a staff_predicate (or social.google_staff_predicate)"
56
+ end
57
+
58
+ account.present? && predicate.call(account) ? true : false
59
+ end
60
+
61
+ private
62
+
63
+ def require_for_staff?
64
+ StandardId.config.google_require_for_staff == true
65
+ end
66
+
67
+ def pinned_google?(auth_method, provider)
68
+ auth_method&.to_sym == :social &&
69
+ provider.to_s == StandardId::Providers::Google.provider_name &&
70
+ StandardId::Providers::Google.pinned?
71
+ end
72
+ end
73
+
74
+ # Combines login_method_policies for a host that runs more than one staff
75
+ # policy (for example this one and StandardId::VoidWhichBinds.staff_policy).
76
+ # Each of those admits a staff account by ITS method and refuses the rest,
77
+ # so chaining them with `fallback:` would lock staff out of the other's
78
+ # method. any_of admits when ANY policy allows, and refuses (with the first
79
+ # refusal's message) only when all do. Non-staff pass every such policy.
80
+ # A policy that raises anything but LoginMethodDenied (a missing predicate,
81
+ # a bug) propagates: the request fails closed.
82
+ class AnyOfPolicy
83
+ def initialize(policies)
84
+ @policies = policies
85
+ raise ArgumentError, "any_of needs at least one policy" if @policies.empty?
86
+ end
87
+
88
+ def call(**context)
89
+ denial = nil
90
+ @policies.each do |policy|
91
+ allowed = policy.call(**StandardId::Utils::CallableParameterFilter.filter(policy, context))
92
+ return true if allowed
93
+ denial ||= StandardId::LoginMethodDenied.new
94
+ rescue StandardId::LoginMethodDenied => e
95
+ denial ||= e
96
+ end
97
+ raise denial
98
+ end
99
+ end
100
+
101
+ def self.staff_policy(staff_predicate: nil, fallback: nil)
102
+ StaffPolicy.new(staff_predicate: staff_predicate, fallback: fallback)
103
+ end
104
+
105
+ def self.any_of(*policies)
106
+ AnyOfPolicy.new(policies.flatten)
107
+ end
108
+ end
109
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module StandardId
4
4
  module Google
5
- VERSION = "0.5.0"
5
+ VERSION = "0.7.0"
6
6
  end
7
7
  end
@@ -3,6 +3,7 @@ require "active_support/core_ext/hash/indifferent_access"
3
3
  require "standard_id"
4
4
  require "standard_id/google/version"
5
5
  require "standard_id/google/providers/google"
6
+ require "standard_id/google/staff_policy"
6
7
 
7
8
  # Registers the provider from a Railtie's after_initialize (a no-op outside
8
9
  # Rails). Its config fields are declared earlier, before config/initializers,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id-google
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '8.0'
18
+ version: '8.1'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: '8.0'
25
+ version: '8.1'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: standard_id
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -53,6 +53,7 @@ files:
53
53
  - lib/generators/standard_id/google/install/templates/initializer.rb.erb
54
54
  - lib/standard_id/google.rb
55
55
  - lib/standard_id/google/providers/google.rb
56
+ - lib/standard_id/google/staff_policy.rb
56
57
  - lib/standard_id/google/version.rb
57
58
  homepage: https://github.com/rarebit-one/standard_id_google
58
59
  licenses:
@@ -76,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
77
  - !ruby/object:Gem::Version
77
78
  version: '0'
78
79
  requirements: []
79
- rubygems_version: 4.0.3
80
+ rubygems_version: 4.0.10
80
81
  specification_version: 4
81
82
  summary: Google Sign In provider plugin for the StandardId engine.
82
83
  test_files: []