standard_id-google 0.6.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 +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +53 -0
- data/lib/generators/standard_id/google/install/templates/initializer.rb.erb +4 -0
- data/lib/standard_id/google/providers/google.rb +94 -2
- data/lib/standard_id/google/staff_policy.rb +109 -0
- data/lib/standard_id/google/version.rb +1 -1
- data/lib/standard_id/google.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40c4313fc9b3b04605a0a36353aedfbec7cfe109f1fdd3471455c28a2c908fc8
|
|
4
|
+
data.tar.gz: 21cf43e45c1f071ad0aec27c322c42e6ff719494da7941ee7f24b9f6a80d8cae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7630091992ad0beaf567eee9e89acdcdf73625b85119a0e5ace6e209b4301f974dfe69d4efa3b679e7db7affcb93bf7028940c4f21c51e526cad8c87a58b618c
|
|
7
|
+
data.tar.gz: 29a6a81e3224960ec686fd3e700a49ea1868e4ea19643ec5e1cfa3986c4c1b80f5e407bcdedd2bdbe8839b723f0ab5e49700fbbfb173e8d157777417bb355c1f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ 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
|
+
|
|
10
37
|
## [0.6.0] - 2026-10-02
|
|
11
38
|
|
|
12
39
|
Pairs with **standard_id 0.44.0** (security fix L1-01), which matches returning
|
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:
|
|
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"],
|
|
@@ -173,6 +232,39 @@ module StandardId
|
|
|
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
|
|
@@ -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
|
data/lib/standard_id/google.rb
CHANGED
|
@@ -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.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jaryl Sim
|
|
@@ -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:
|