standard_id-google 0.3.0 → 0.5.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: 88bce7685da60d2df6e41c103d179a5baa6bfb71d9e7f82ecaabb43e280680c7
4
- data.tar.gz: 55a57eaba19c3907515a9609548e4d1370adaf400aa3481125e3d2a42e866c9b
3
+ metadata.gz: f22ca920dcb306788b3588bf99ac704b9ce5e40cbb9c423a551e064ab6b236a9
4
+ data.tar.gz: f8492f9d9af4dbf74af4062524e0c217e20e50ec3420605a76d363154cacc2f5
5
5
  SHA512:
6
- metadata.gz: 1453686923aaf994a195d5d8d811fce1d1f02a4128eba1bcecffe457a533bad0a9345be8ae6f2545375e5c65d5357a170d450f18ca5ae71ae322f822b5a2013c
7
- data.tar.gz: 1d23bfa48f33aab5980bbd9f6a4e5594dba9259dbe8cac91b434a3700f13187884972ca783674bc29986ebfc7c22040bf516445c338cc0d24ae15eb1099a0f9f
6
+ metadata.gz: 9b8d7e1bb7bdfa6302108521bf603a8727a61cd2a6893dcfd8ed230f9999697aa627f912b870528833f9b4781cbe1dbd868f761bdec013ae6cb7ff3a2f8af004
7
+ data.tar.gz: 53b44b47d85d30da76de8dbdf1f55953c3b3f4a31fb3808c769b8a72175210d8ac0fdbbbbbe0d52eb7e45817666d17edd5ce2947b1dfc16462d3f12d92226544
data/CHANGELOG.md CHANGED
@@ -7,6 +7,167 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-09-24
11
+
12
+ Adopts the provider-plugin API of standard_id 0.42.
13
+
14
+ ### Upgrade
15
+
16
+ - **Requires `standard_id` 0.42** (`~> 0.42`, was `>= 0.29, < 1.0`). Bump both
17
+ together.
18
+ - **Rename `GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` to
19
+ `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`** when convenient. Initializers
20
+ from the 0.4.0 generator assign the old names explicitly and keep working
21
+ unchanged; the old names are also read as a deprecated ENV fallback (see
22
+ Deprecated). sidekick-web already uses the canonical names.
23
+ - **Expect a boot warning if the secret is missing.** With `google_client_id`
24
+ set and `google_client_secret` blank, standard_id now logs a warning at boot
25
+ (raises in production under `c.social.provider_misconfiguration = :raise`).
26
+ An app that deliberately accepts only native ID tokens and has no secret
27
+ should keep that setting at `:warn`.
28
+ - **Specs stubbing the tokeninfo call:** access-token verification now posts to
29
+ `TOKEN_INFO_ENDPOINT` (`https://oauth2.googleapis.com/tokeninfo`), not
30
+ `https://www.googleapis.com/oauth2/v3/tokeninfo`. ID-token stubs against
31
+ `TOKEN_INFO_ENDPOINT` (sidekick-web's `social_callback_spec.rb`) are
32
+ unaffected.
33
+ - **Anyone rescuing on message text:** several messages changed (see Changed).
34
+ `StandardId::Google::Railtie` no longer exists.
35
+
36
+ ### Added
37
+
38
+ - **Required config field.** `google_client_secret` is declared
39
+ `required: true`, so `StandardId::Providers::Google.configuration_errors` and
40
+ standard_id's boot check report it whenever `google_client_id` (the enabling
41
+ field) is set.
42
+ - **ENV fallback** through standard_id 0.42: `GOOGLE_CLIENT_ID`,
43
+ `GOOGLE_CLIENT_SECRET`.
44
+ - Specs for `resolve_params`, `skip_csrf?`, `supports_mobile_callback?` /
45
+ `flow_for`, nonce handling (web code exchange included) and configuration,
46
+ plus standard_id's `"a registered StandardId provider"` shared example.
47
+
48
+ ### Changed
49
+
50
+ - **One tokeninfo endpoint.** ID tokens and access tokens are both verified
51
+ against `TOKEN_INFO_ENDPOINT`, `https://oauth2.googleapis.com/tokeninfo` —
52
+ the endpoint Google documents for both. Access tokens previously went to a
53
+ hard-coded `https://www.googleapis.com/oauth2/v3/tokeninfo`.
54
+ - **The id_token and access_token flows no longer need `google_client_secret`.**
55
+ Both verify through tokeninfo and compare the audience to the client ID; only
56
+ the web code exchange sends the secret. Previously every flow raised
57
+ `Google provider is not configured` without it.
58
+ - **Nonce mismatches no longer leak the nonce.** The message was
59
+ `ID token nonce mismatch. Expected: <nonce>, got: <nonce>`; it is now
60
+ standard_id's `ID token nonce mismatch`, and the comparison is constant-time.
61
+ The nonce is now checked after the audience and issuer.
62
+ - **Audience and issuer errors no longer echo values** — the old messages
63
+ included this app's client ID and the presented `aud`/`iss`. A token with no
64
+ `aud` at all is rejected explicitly.
65
+ - **The duplicated helpers are gone** in favour of standard_id's
66
+ `Providers::Base`: `rescue_to_oauth_error`, `verify_nonce!`,
67
+ `build_authorization_url`, `extract_tokens` (the private
68
+ `extract_token_payload` is removed). `lib/standard_id/google/railtie.rb` is
69
+ replaced by `StandardId::Providers.plugin_railtie(:google, ...)`.
70
+ - **Error messages**, now consistently Google-prefixed:
71
+ - `Either code, id_token, or access_token must be provided` → `Google sign-in requires a code, an id_token or an access_token`
72
+ - `Google provider is not configured` → `Google OAuth is not configured` (client ID missing) or `Google OAuth credentials are incomplete: google_client_secret not set` (code exchange only)
73
+ - `Missing authorization code` → `Google authorization code is missing`
74
+ - `Failed to exchange Google authorization code` → `...: <error>` (Google's `error`, else `HTTP <status>`)
75
+ - `Google response missing access token` → `Google token response is missing access_token`
76
+ - `Missing id_token` → `Google id_token is missing`
77
+ - `Invalid or expired id_token` → `Invalid Google ID token: invalid or expired`
78
+ - `ID token audience mismatch. Expected: ..., got: ...` → `Invalid Google ID token audience`
79
+ - `ID token issuer invalid. Expected Google, got: ...` → `Invalid Google ID token issuer`
80
+ - `Missing access token` → `Google access token is missing`
81
+ - `Invalid or expired access token` → `Invalid Google access token: invalid or expired`
82
+ - `Access token audience mismatch. Expected: ..., got: ...` → `Invalid Google access token audience`
83
+ - `Failed to fetch Google user info` → `...: HTTP <status>`
84
+ - **Spec layout mirrors `lib/`** and standard_id-apple:
85
+ `spec/standard_id/providers/google_spec.rb` →
86
+ `spec/standard_id/google/providers/google_spec.rb`, plus
87
+ `spec/standard_id/google/registration_spec.rb`.
88
+ - Install generator and README use the canonical `GOOGLE_CLIENT_ID` /
89
+ `GOOGLE_CLIENT_SECRET`, and document the ENV fallback, required field and
90
+ flows.
91
+
92
+ ### Deprecated
93
+
94
+ - **`GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET`** as ENV sources.
95
+ Read only when the field is never assigned and the canonical variable is
96
+ unset, with one warning per variable per process through
97
+ `StandardId.deprecator`.
98
+
99
+ ### Removed
100
+
101
+ - `StandardId::Google::Railtie` (replaced by the Railtie `plugin_railtie`
102
+ defines, `StandardId::Providers::Railties::Google`).
103
+
104
+ ## [0.4.0] - 2026-07-31
105
+
106
+ ### Added
107
+
108
+ - **Install generator: `bin/rails g standard_id:google:install`.** Writes
109
+ `config/initializers/standard_id_google.rb` with the `social.google_*` fields
110
+ wired to ENV, then prints the environment variables the host has to set and
111
+ the reminder that `/auth/callback/google` must be registered as an authorized
112
+ redirect URI for **every** origin served — the Google console matches the
113
+ redirect exactly, so staging and production each need their own. Idempotent:
114
+ re-running skips an existing initializer; `--force` overwrites,
115
+ `--skip-initializer` writes nothing.
116
+
117
+ It writes a **separate** file rather than editing `standard_id.rb`, so the
118
+ provider can be removed by deleting one file and `standard_id`'s own install
119
+ generator stays free to overwrite its initializer without clobbering these
120
+ credentials. Initializers load alphabetically, so the base config is applied
121
+ first. The generated file uses the `config.social.` form throughout — a spec
122
+ pins that it never emits the unqualified `config.google_*` form, which works
123
+ today only because the names happen to be unique across scopes.
124
+
125
+ Five of the nine `standard_*` gems shipped an install generator and this was
126
+ not one of them, which left both consumers assembling the block from the
127
+ README by hand.
128
+
129
+ ### Documentation
130
+
131
+ - **Consumer list corrected in `CLAUDE.md`: this gem has two consumers, not
132
+ one.** It named `luminality-web` only; `sidekick-web` also consumes it. Both
133
+ live in sibling workspaces rather than beside this repo, which is how the
134
+ second one went unnoticed.
135
+
136
+ - **Corrected the Configuration section, which showed a form that raised on
137
+ `standard_id` <= 0.32.0.** These fields are declared by this gem, and until
138
+ `standard_id` 0.33.0 they were declared from this gem's Railtie
139
+ `after_initialize` — after `config/initializers` — so the documented plain
140
+ initializer raised `StandardId::ConfigurationError: Unknown field
141
+ 'google_client_id' for scope 'social'`. The README now records the
142
+ `after_initialize` workaround for older `standard_id`, states that 0.33.0
143
+ declares provider fields before `:load_config_initializers` so the plain form
144
+ is correct there, and notes that the fields do not exist at all without this
145
+ gem in the Gemfile — on any `standard_id` version.
146
+
147
+ ### Changed
148
+
149
+ - **`standard_id` dependency tightened from `~> 0.1, >= 0.1.7` to `~> 0.29.0`.**
150
+ The old constraint claimed compatibility with every `0.x` release while this
151
+ plugin reaches into `StandardId::ProviderRegistry` and
152
+ `StandardId::Providers::Google`, and `standard_id` is pre-1.0 with breaking
153
+ minors. Bundler would happily resolve against an untested minor and fail at
154
+ runtime instead of at resolution. Both current consumers already pin
155
+ `standard_id "~> 0.29.0"`, so nothing existing is affected.
156
+
157
+ ### Fixed
158
+
159
+ - Gemspec no longer packages the `.claude/` directory. Published `0.3.0` shipped
160
+ `.claude/settings.json`, `.claude/hooks/enforce-worktree.sh`, and three skill
161
+ files to every consumer — this gem's `spec.files` reject-list was the only one
162
+ in the `standard_*` family missing the `.claude/` prefix (`standard_id-apple`
163
+ already had it). Packaged file count drops 21 → 16; `lib/` and `LICENSE` are
164
+ unaffected.
165
+ - Gemspec now uses an allow-list (`Dir["lib/**/*", …]`) rather than a
166
+ `git ls-files` reject-list, so packaging fails **closed** and this class of
167
+ leak cannot recur. Also drops `.editorconfig`, `.pinact.yaml`, `.rspec`,
168
+ `.rubocop.yml`, `.ruby-version`, `AGENTS.md`, `CLAUDE.md`, and
169
+ `CODE_OF_CONDUCT.md`; `lib/` is byte-identical.
170
+
10
171
  ## [0.3.0] - 2026-04-29
11
172
 
12
173
  ### Added
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) Jaryl Sim
3
+ Copyright (c) 2026 Rarebit One
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -4,6 +4,8 @@ This gem extracts the Google OAuth provider from the core [`standard_id`](https:
4
4
 
5
5
  ## Installation
6
6
 
7
+ Requires `standard_id` 0.42 or later.
8
+
7
9
  Add the gem next to `standard_id`:
8
10
 
9
11
  ```ruby
@@ -20,21 +22,127 @@ bundle install
20
22
 
21
23
  The gem automatically registers itself with StandardId when it is required.
22
24
 
25
+ Then run the install generator to drop the credentials block in place:
26
+
27
+ ```bash
28
+ bin/rails g standard_id:google:install
29
+ ```
30
+
31
+ This writes `config/initializers/standard_id_google.rb` — deliberately a
32
+ separate file from `config/initializers/standard_id.rb`, so the provider can be
33
+ removed by deleting one file and `standard_id`'s own install generator stays
34
+ free to overwrite its initializer without clobbering these values. Initializers
35
+ load alphabetically, so the base config is applied first. The generator is
36
+ idempotent; re-running on an existing initializer skips with a clear message
37
+ (pass `--force` to overwrite).
38
+
23
39
  ## Configuration
24
40
 
25
- Configure your Google credentials inside the StandardId configuration block:
41
+ The generator writes this for you; the block is documented here for hosts
42
+ configuring by hand. Configure your Google credentials inside the StandardId
43
+ configuration block:
26
44
 
27
45
  ```ruby
46
+ # config/initializers/standard_id_google.rb
28
47
  StandardId.configure do |config|
29
- config.social.google_client_id = ENV.fetch("GOOGLE_OAUTH_CLIENT_ID", nil)
30
- config.social.google_client_secret = ENV.fetch("GOOGLE_OAUTH_CLIENT_SECRET", nil)
48
+ config.social.google_client_id = ENV.fetch("GOOGLE_CLIENT_ID", nil)
49
+ config.social.google_client_secret = ENV.fetch("GOOGLE_CLIENT_SECRET", nil)
31
50
  end
32
51
  ```
33
52
 
34
53
  With those values in place, StandardId routes such as `/auth/callback/google` continue to function using this provider gem.
35
54
 
55
+ ### ENV fallback
56
+
57
+ On `standard_id` 0.42+, a field you never assign falls back to the ENV
58
+ variable named after it, upper-cased:
59
+
60
+ | Field | ENV variable | Deprecated fallback |
61
+ |---|---|---|
62
+ | `google_client_id` | `GOOGLE_CLIENT_ID` | `GOOGLE_OAUTH_CLIENT_ID` |
63
+ | `google_client_secret` | `GOOGLE_CLIENT_SECRET` | `GOOGLE_OAUTH_CLIENT_SECRET` |
64
+
65
+ So with those variables set the block above is optional. Explicit
66
+ configuration, even `nil`, always wins. The `GOOGLE_OAUTH_*` names — what
67
+ install generators before 0.5.0 wrote — are still read when the canonical
68
+ variable is unset and the field is never assigned, with a deprecation warning;
69
+ rename them. (An initializer that assigns `ENV.fetch("GOOGLE_OAUTH_CLIENT_ID")`
70
+ explicitly keeps working as-is.)
71
+
72
+ ### Required fields and the boot check
73
+
74
+ `google_client_id` switches the provider on — it is what
75
+ `StandardId.social_provider_enabled?(:google)` and the `google_enabled` Inertia
76
+ prop report. While it is set, `google_client_secret` is required: the web
77
+ sign-in's code exchange needs it, so without it the user authenticates with
78
+ Google and only then does the callback fail. StandardId checks this once every
79
+ plugin has registered:
80
+
81
+ ```ruby
82
+ StandardId::Providers::Google.configuration_errors
83
+ # => ["google_client_secret is required when google_client_id is set"]
84
+
85
+ config.social.provider_misconfiguration = :raise # fail a production boot instead of warning
86
+ ```
87
+
88
+ ### Flows
89
+
90
+ | Flow | Needs `google_client_secret` |
91
+ |---|---|
92
+ | Web (`/auth/callback/google`, authorization code) | yes |
93
+ | Native `id_token` (`/api/oauth/callback/google`) | no |
94
+ | Native `access_token` | no |
95
+
96
+ ID tokens and access tokens are both checked against Google's tokeninfo
97
+ endpoint (`https://oauth2.googleapis.com/tokeninfo`), then against this app's
98
+ client ID. An app that only accepts native ID tokens can run without a client
99
+ secret; it will get the boot warning above, because an enabled provider shows
100
+ the web sign-in button — leave `provider_misconfiguration` at `:warn` there.
101
+
102
+ ### Boot ordering (standard_id <= 0.32.0)
103
+
104
+ These fields are declared by *this gem*, not by `standard_id`, and until
105
+ `standard_id` 0.33.0 they were declared from this gem's Railtie
106
+ `after_initialize` — which runs *after* `config/initializers`. On `standard_id`
107
+ **0.32.0 and earlier** the block above therefore raised:
108
+
109
+ ```
110
+ StandardId::ConfigurationError: Unknown field 'google_client_id' for scope 'social'
111
+ ```
112
+
113
+ The workaround was to wrap the writes:
114
+
115
+ ```ruby
116
+ # Only needed on standard_id <= 0.32.0
117
+ Rails.application.config.after_initialize do
118
+ StandardId.configure do |config|
119
+ config.social.google_client_id = ENV.fetch("GOOGLE_CLIENT_ID", nil)
120
+ config.social.google_client_secret = ENV.fetch("GOOGLE_CLIENT_SECRET", nil)
121
+ end
122
+ end
123
+ ```
124
+
125
+ `standard_id` **>= 0.33.0** declares every loaded provider's fields before
126
+ `:load_config_initializers`, so a plain initializer is correct. The wrapper is no
127
+ longer needed and existing ones keep working unchanged.
128
+
129
+ Note this is about *ordering*, not just versions: **the fields do not exist
130
+ without this gem in your Gemfile**, on any `standard_id` version. Configuring
131
+ `social.google_*` with the plugin absent raises the same error, correctly.
132
+
36
133
  ## Testing
37
134
 
135
+ In a host app, pin the plugin's registration with standard_id's shared
136
+ example:
137
+
138
+ ```ruby
139
+ require "standard_id/testing"
140
+
141
+ RSpec.describe "StandardId social providers" do
142
+ it_behaves_like "a registered StandardId provider", :google
143
+ end
144
+ ```
145
+
38
146
  Run the provider test suite with:
39
147
 
40
148
  ```bash
@@ -53,4 +161,4 @@ To release a new version:
53
161
 
54
162
  ## License
55
163
 
56
- The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
164
+ The gem is available as open source under the terms of the [MIT License](LICENSE).
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module StandardId
6
+ module Google
7
+ module Generators
8
+ # Installs the Google provider's credentials block in a host Rails app.
9
+ #
10
+ # Writes `config/initializers/standard_id_google.rb` — a separate file
11
+ # from `config/initializers/standard_id.rb` on purpose. The provider is
12
+ # opt-in per app, so its credentials should be removable by deleting one
13
+ # file, and `standard_id`'s own install generator must stay free to
14
+ # overwrite its initializer without clobbering these values.
15
+ #
16
+ # Initializers load alphabetically, so `standard_id.rb` runs before
17
+ # `standard_id_google.rb` — the base configuration is already applied by
18
+ # the time this file sets the `social.google_*` fields.
19
+ #
20
+ # Idempotent: re-running skips an initializer that is already there.
21
+ # `--skip-initializer` opts out; `--force` overwrites.
22
+ class InstallGenerator < Rails::Generators::Base
23
+ source_root File.expand_path("templates", __dir__)
24
+
25
+ INITIALIZER_PATH = "config/initializers/standard_id_google.rb"
26
+
27
+ desc <<~DESC
28
+ Installs StandardId Google. This writes #{INITIALIZER_PATH} with the
29
+ social.google_* fields wired to ENV, and prints the env vars the host
30
+ needs to set.
31
+
32
+ The generator is idempotent — an existing initializer is skipped with
33
+ a clear message. Pass --force to overwrite.
34
+ DESC
35
+
36
+ class_option :skip_initializer, type: :boolean, default: false,
37
+ desc: "Do not write #{INITIALIZER_PATH}"
38
+ class_option :force, type: :boolean, default: false,
39
+ desc: "Overwrite #{INITIALIZER_PATH} if it already exists"
40
+
41
+ def copy_initializer
42
+ if options[:skip_initializer]
43
+ say_status("skip", "#{INITIALIZER_PATH} (--skip-initializer)", :yellow)
44
+ return
45
+ end
46
+
47
+ if File.exist?(File.join(destination_root, INITIALIZER_PATH)) && !options[:force]
48
+ say_status("identical", "#{INITIALIZER_PATH} (already exists; pass --force to overwrite)", :blue)
49
+ return
50
+ end
51
+
52
+ template "initializer.rb.erb", INITIALIZER_PATH, force: options[:force]
53
+ end
54
+
55
+ def print_env_hint
56
+ return if options[:skip_initializer]
57
+
58
+ say ""
59
+ say "=" * 79
60
+ say "StandardId Google installed."
61
+ say ""
62
+ say "Set these in the host's environment (1Password / DO app spec / .env):"
63
+ say ""
64
+ say " GOOGLE_CLIENT_ID the OAuth 2.0 Web application client ID"
65
+ say " GOOGLE_CLIENT_SECRET its client secret — required whenever GOOGLE_CLIENT_ID"
66
+ say " is set; StandardId checks for it at boot"
67
+ say ""
68
+ say "Register /auth/callback/google as an authorized redirect URI in the"
69
+ say "Google Cloud console for EVERY origin you serve — the console matches"
70
+ say "the redirect exactly, so staging and production each need their own."
71
+ say "=" * 79
72
+ say ""
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Google Sign In credentials for StandardId.
4
+ #
5
+ # These two fields are declared by the standard_id-google gem, NOT by
6
+ # standard_id — they do not exist unless this gem is in the Gemfile, on any
7
+ # standard_id version. Removing the gem and leaving this file behind raises
8
+ # `StandardId::ConfigurationError: Unknown field 'google_client_id' for scope
9
+ # 'social'`, which is correct behaviour, not a bug.
10
+ #
11
+ # Set them in the `social` scope, as below. The unqualified form
12
+ # (`config.google_client_id = ...`) happens to work today because StandardId
13
+ # routes a unique unqualified name to its owning scope, but it stops working
14
+ # the day another scope declares a colliding name. Use `config.social.`.
15
+ #
16
+ # ENV: on standard_id >= 0.42 a field this file does not assign falls back to
17
+ # the ENV variable named after it, upper-cased — GOOGLE_CLIENT_ID and
18
+ # GOOGLE_CLIENT_SECRET — which are the names used below. The assignments are
19
+ # kept so the wiring is visible; delete them to leave it to the fallback, or
20
+ # point them at another source (e.g. Rails credentials). Installs before
21
+ # standard_id-google 0.5.0 used GOOGLE_OAUTH_CLIENT_ID / _SECRET.
22
+ #
23
+ # google_client_id switches the provider on. While it is set, StandardId's boot
24
+ # check requires google_client_secret, which the web sign-in's code exchange
25
+ # needs (warns by default; `config.social.provider_misconfiguration = :raise`
26
+ # fails a production boot instead). Native id_token / access_token sign-in
27
+ # needs only the client ID.
28
+ StandardId.configure do |config|
29
+ config.social.google_client_id = ENV.fetch("GOOGLE_CLIENT_ID", nil)
30
+ config.social.google_client_secret = ENV.fetch("GOOGLE_CLIENT_SECRET", nil)
31
+ end