standard_id 0.51.2 → 0.53.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 +50 -0
- data/README.md +65 -5
- data/lib/generators/standard_id/install/install_generator.rb +4 -1
- data/lib/generators/standard_id/install/templates/standard_id.rb +13 -558
- data/lib/generators/standard_id/install/templates/standard_id_reference.rb +581 -0
- data/lib/standard_id/app_environment.rb +26 -0
- data/lib/standard_id/config/presets.rb +45 -0
- data/lib/standard_id/config/schema.rb +9 -9
- data/lib/standard_id/config_schema.rb +10 -0
- data/lib/standard_id/engine.rb +1 -1
- data/lib/standard_id/passwordless/verification_service.rb +4 -4
- data/lib/standard_id/provider_registry.rb +1 -1
- data/lib/standard_id/testing/request_helpers.rb +99 -0
- data/lib/standard_id/version.rb +1 -1
- data/lib/standard_id.rb +2 -0
- metadata +4 -1
|
@@ -1,571 +1,26 @@
|
|
|
1
1
|
# StandardId configuration
|
|
2
2
|
# Generated by: rails g standard_id:install
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Keep only the settings you change here; everything else is the gem default.
|
|
5
|
+
# Every setting, its default and the reasoning: the README "Configuration"
|
|
6
|
+
# section, lib/standard_id/config/schema.rb, and the annotated initializer at
|
|
7
|
+
# lib/generators/standard_id/install/templates/standard_id_reference.rb (all in
|
|
8
|
+
# the gem: `bundle open standard_id`).
|
|
6
9
|
|
|
7
10
|
StandardId.configure do |c|
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
+
# An Inertia app whose users sign in passwordlessly or socially can start
|
|
12
|
+
# from a preset. :inertia_web sets use_inertia, alias_current_user,
|
|
13
|
+
# web.signup/password_login/password_reset = false and
|
|
14
|
+
# oauth.discovery_endpoint_base = :request. Anything you assign yourself
|
|
15
|
+
# wins, before or after this line.
|
|
16
|
+
# c.preset :inertia_web
|
|
11
17
|
|
|
12
18
|
# The ActiveRecord class that represents a user/account in your app. Must
|
|
13
|
-
# include StandardId::AccountAssociations (see post-install checklist).
|
|
14
|
-
# Default: "User"
|
|
19
|
+
# include StandardId::AccountAssociations (see the post-install checklist).
|
|
15
20
|
c.account_class_name = "User"
|
|
16
|
-
# The enum key StandardId::AccountStatus uses for a deactivated account
|
|
17
|
-
# (default :inactive). Set it to the value your status column already
|
|
18
|
-
# carries, e.g. :suspended, to adopt the concern without renaming it.
|
|
19
|
-
# c.inactive_account_status = :inactive
|
|
20
21
|
|
|
21
|
-
# JWT "iss" claim
|
|
22
|
+
# JWT "iss" claim, typically your application's canonical base URL. When
|
|
22
23
|
# set, tokens embed it and verification enforces it. Strongly recommended
|
|
23
24
|
# for production; otherwise the engine logs a warning on boot.
|
|
24
|
-
# Default: nil
|
|
25
25
|
# c.issuer = "https://auth.example.com"
|
|
26
|
-
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
# Discovery documents (/.well-known/openid-configuration and
|
|
29
|
-
# /.well-known/oauth-authorization-server)
|
|
30
|
-
# ---------------------------------------------------------------------------
|
|
31
|
-
# READ THIS IF YOU MOUNT ApiEngine UNDER A PREFIX (/api, /api/v1, ...).
|
|
32
|
-
#
|
|
33
|
-
# The issuer and the endpoint base are different things. `issuer` is a stable
|
|
34
|
-
# security identifier (RFC 8414 §2) that clients compare byte-for-byte with the
|
|
35
|
-
# URL they used for discovery and with the `iss` claim of issued tokens. It is
|
|
36
|
-
# never derived from the request and cannot be overridden.
|
|
37
|
-
#
|
|
38
|
-
# `discovery_endpoint_base` is where /authorize, /oauth/token etc. ACTUALLY
|
|
39
|
-
# are. It defaults to the issuer, which is right only if your issuer carries
|
|
40
|
-
# the mount path (e.g. "https://host/auth/api"). If it does not, the document
|
|
41
|
-
# advertises endpoints at the bare origin and every one of them 404s.
|
|
42
|
-
#
|
|
43
|
-
# nil (default) — the issuer
|
|
44
|
-
# :request — request.base_url + the detected mount path <- prefixed mounts
|
|
45
|
-
# "https://..." — verbatim
|
|
46
|
-
# callable — receives (request:); for a proxy that rewrites base_url, or
|
|
47
|
-
# an app mounting ApiEngine more than once
|
|
48
|
-
#
|
|
49
|
-
# Default: nil
|
|
50
|
-
# c.oauth.discovery_endpoint_base = :request
|
|
51
|
-
|
|
52
|
-
# Members that cannot be derived. Values static or callable; a callable gets
|
|
53
|
-
# { origin:, endpoint_base:, issuer:, request: } (origin has no path). A nil
|
|
54
|
-
# value REMOVES the member rather than emitting null. Setting `issuer` raises.
|
|
55
|
-
# Default: {}
|
|
56
|
-
# c.oauth.discovery_metadata_overrides = {
|
|
57
|
-
# # a host-owned shim that injects the audience before handing off to the
|
|
58
|
-
# # engine's /authorize
|
|
59
|
-
# authorization_endpoint: ->(ctx) { "#{ctx[:origin]}/oauth/authorize" },
|
|
60
|
-
# # narrower than everything the server can mint
|
|
61
|
-
# scopes_supported: %w[mcp mcp:read],
|
|
62
|
-
# # MUST mirror your dynamic-registration policy, or spec-following clients
|
|
63
|
-
# # register in a way that policy then rejects
|
|
64
|
-
# token_endpoint_auth_methods_supported: %w[none],
|
|
65
|
-
# # omit a member entirely
|
|
66
|
-
# userinfo_endpoint: nil
|
|
67
|
-
# }
|
|
68
|
-
#
|
|
69
|
-
# RFC 8615 clients probe the ORIGIN ROOT, which is outside every engine mount,
|
|
70
|
-
# so those routes must be drawn in config/routes.rb — the gem cannot:
|
|
71
|
-
#
|
|
72
|
-
# standard_id_well_known_routes at: "/api" # your ApiEngine mount path
|
|
73
|
-
#
|
|
74
|
-
# That draws the root form AND the RFC 8414 §3.1 path-inserted form
|
|
75
|
-
# (/.well-known/oauth-authorization-server/api). The second looks redundant
|
|
76
|
-
# and is not: it is the URL Claude Code actually requests against a
|
|
77
|
-
# path-carrying issuer. Accepts only:/except: (:oauth_authorization_server,
|
|
78
|
-
# :openid_configuration, :jwks) — e.g. `only: :jwks` if you keep your own
|
|
79
|
-
# metadata controller. See docs/MIGRATION_GUIDE.md.
|
|
80
|
-
|
|
81
|
-
# Login URL used for redirects when authentication is required but missing.
|
|
82
|
-
# Default: "/login"
|
|
83
|
-
# c.login_url = "/login"
|
|
84
|
-
|
|
85
|
-
# ---------------------------------------------------------------------------
|
|
86
|
-
# Infrastructure
|
|
87
|
-
# ---------------------------------------------------------------------------
|
|
88
|
-
|
|
89
|
-
# Cache backend for rate limits, JWKS, etc. Falls back to Rails.cache.
|
|
90
|
-
# Default: Rails.cache
|
|
91
|
-
# c.cache_store = Rails.cache
|
|
92
|
-
|
|
93
|
-
# Logger used by StandardId. Falls back to Rails.logger.
|
|
94
|
-
# Default: Rails.logger
|
|
95
|
-
# c.logger = Rails.logger
|
|
96
|
-
|
|
97
|
-
# Layout used by built-in ERB web views. Ignored when use_inertia is true.
|
|
98
|
-
# Default: nil (StandardId's packaged layout)
|
|
99
|
-
# c.web_layout = "application"
|
|
100
|
-
|
|
101
|
-
# Expose current_account as current_user (useful for ActionPolicy / Pundit).
|
|
102
|
-
# Default: false
|
|
103
|
-
# c.alias_current_user = true
|
|
104
|
-
|
|
105
|
-
# Opt StandardId's OWN associations out of an app-wide
|
|
106
|
-
# `strict_loading_by_default = true`.
|
|
107
|
-
#
|
|
108
|
-
# Affects Account#identifiers/credentials/sessions/refresh_tokens/
|
|
109
|
-
# client_applications, StandardId::Session#refresh_tokens and
|
|
110
|
-
# StandardId::Identifier#credentials. Set it to false if strict loading is on
|
|
111
|
-
# app-wide and you don't want to eager-load the gem's internals at every call
|
|
112
|
-
# site (the gem lazy-loads some of them itself).
|
|
113
|
-
#
|
|
114
|
-
# Tri-state, and nil is NOT the same as false:
|
|
115
|
-
# nil — declare no `strict_loading:` option at all; the associations
|
|
116
|
-
# inherit your model's setting, exactly as before this option
|
|
117
|
-
# existed. This is the default and preserves existing behaviour.
|
|
118
|
-
# false — opt out: these associations may lazy-load even with strict
|
|
119
|
-
# loading on app-wide.
|
|
120
|
-
# true — opt in: force strict loading on them even if it is off app-wide.
|
|
121
|
-
#
|
|
122
|
-
# Must be set HERE, in an initializer — not in an `after_initialize` block.
|
|
123
|
-
# The associations read it when your Account class body runs, so a later
|
|
124
|
-
# assignment has no effect. StandardId raises a ConfigurationError at boot if
|
|
125
|
-
# it detects that, rather than letting it fail silently.
|
|
126
|
-
#
|
|
127
|
-
# Default: nil
|
|
128
|
-
# c.association_strict_loading = false
|
|
129
|
-
|
|
130
|
-
# Default scope applied when loading the current account — eager-load the
|
|
131
|
-
# associations your views/controllers touch to avoid N+1s.
|
|
132
|
-
# Default: nil
|
|
133
|
-
# c.account_scope = ->(scope) { scope.includes(:profiles, :identifiers) }
|
|
134
|
-
|
|
135
|
-
# Inertia.js support (requires the inertia_rails gem). When enabled, web
|
|
136
|
-
# controllers render Inertia components instead of ERB views. Provide the
|
|
137
|
-
# matching frontend components (e.g. pages/auth/login/show.tsx).
|
|
138
|
-
# Defaults: use_inertia = false, inertia_component_namespace = "standard_id"
|
|
139
|
-
# c.use_inertia = true
|
|
140
|
-
# c.inertia_component_namespace = "auth"
|
|
141
|
-
|
|
142
|
-
# ---------------------------------------------------------------------------
|
|
143
|
-
# Web engine feature toggles
|
|
144
|
-
# ---------------------------------------------------------------------------
|
|
145
|
-
# Each toggle enables or disables a built-in WebEngine route group. Turn
|
|
146
|
-
# off features your app handles itself or does not expose.
|
|
147
|
-
|
|
148
|
-
# Default: true — enables /login, /logout and related password routes.
|
|
149
|
-
# c.web.password_login = true
|
|
150
|
-
|
|
151
|
-
# Default: true — enables /signup.
|
|
152
|
-
# c.web.signup = true
|
|
153
|
-
|
|
154
|
-
# Default: false — enables OTP (email/SMS) login at /passwordless.
|
|
155
|
-
# c.web.passwordless_login = false
|
|
156
|
-
|
|
157
|
-
# Default: false — allows new account registration via passwordless OTP.
|
|
158
|
-
# c.web.passwordless_registration = false
|
|
159
|
-
|
|
160
|
-
# Default: true — enables /auth/:provider social login routes.
|
|
161
|
-
# c.web.social_login = true
|
|
162
|
-
|
|
163
|
-
# Default: true — enables /password/reset flow.
|
|
164
|
-
# c.web.password_reset = true
|
|
165
|
-
|
|
166
|
-
# Default: true — enables email verification endpoints.
|
|
167
|
-
# c.web.email_verification = true
|
|
168
|
-
|
|
169
|
-
# Default: true — enables phone verification endpoints.
|
|
170
|
-
# c.web.phone_verification = true
|
|
171
|
-
|
|
172
|
-
# Default: true — enables /sessions management UI.
|
|
173
|
-
# c.web.sessions_management = true
|
|
174
|
-
|
|
175
|
-
# ---------------------------------------------------------------------------
|
|
176
|
-
# Session lifetimes (seconds)
|
|
177
|
-
# ---------------------------------------------------------------------------
|
|
178
|
-
|
|
179
|
-
# Default: 86_400 — 24 hours for browser (cookie) sessions.
|
|
180
|
-
# c.session.browser_session_lifetime = 86_400
|
|
181
|
-
|
|
182
|
-
# Default: 2_592_000 — 30 days for "remember me" cookies.
|
|
183
|
-
# c.session.browser_session_remember_me_lifetime = 2_592_000
|
|
184
|
-
|
|
185
|
-
# Default: 2_592_000 — 30 days for API / mobile device sessions.
|
|
186
|
-
# c.session.device_session_lifetime = 2_592_000
|
|
187
|
-
|
|
188
|
-
# Default: 7_776_000 — 90 days for service-to-service sessions.
|
|
189
|
-
# c.session.service_session_lifetime = 7_776_000
|
|
190
|
-
|
|
191
|
-
# ---------------------------------------------------------------------------
|
|
192
|
-
# Password policy
|
|
193
|
-
# ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
|
-
# Default: 8
|
|
196
|
-
# c.password.minimum_length = 8
|
|
197
|
-
|
|
198
|
-
# Default: true
|
|
199
|
-
# c.password.require_special_chars = true
|
|
200
|
-
|
|
201
|
-
# Default: true
|
|
202
|
-
# c.password.require_uppercase = true
|
|
203
|
-
|
|
204
|
-
# Default: true
|
|
205
|
-
# c.password.require_numbers = true
|
|
206
|
-
|
|
207
|
-
# ---------------------------------------------------------------------------
|
|
208
|
-
# Passwordless (OTP)
|
|
209
|
-
# ---------------------------------------------------------------------------
|
|
210
|
-
|
|
211
|
-
# OTP email delivery mode:
|
|
212
|
-
# :custom — host app handles delivery via event subscriber (default)
|
|
213
|
-
# :built_in — engine sends via PasswordlessMailer
|
|
214
|
-
# Default: :custom
|
|
215
|
-
# c.passwordless.delivery = :built_in
|
|
216
|
-
# c.passwordless.mailer_from = "noreply@example.com"
|
|
217
|
-
# c.passwordless.mailer_subject = "Your sign-in code"
|
|
218
|
-
|
|
219
|
-
# Default: 600 (10 minutes) — TTL of OTP codes.
|
|
220
|
-
# c.passwordless.code_ttl = 600
|
|
221
|
-
|
|
222
|
-
# Default: 6 — length of generated OTP codes (digits). Clamped to 4..10.
|
|
223
|
-
# Codes may be zero-padded (e.g. "000123") so host apps that display or
|
|
224
|
-
# parse codes must treat them as strings, not integers. 8+ is recommended
|
|
225
|
-
# for security-sensitive deployments.
|
|
226
|
-
# c.passwordless.code_length = 6
|
|
227
|
-
|
|
228
|
-
# Per-challenge global attempt ceiling (across all IPs). When reached the
|
|
229
|
-
# challenge is burned so further submissions fail fast. Distinct from the
|
|
230
|
-
# per-IP rate limit (c.rate_limits.otp_verify_per_ip) — this defends
|
|
231
|
-
# against distributed brute-force against a single challenge.
|
|
232
|
-
# Default: nil — falls back to :max_attempts (default 3) for backwards
|
|
233
|
-
# compatibility, so the effective default ceiling is 3.
|
|
234
|
-
# c.passwordless.max_attempts_per_challenge = 3
|
|
235
|
-
|
|
236
|
-
# Default: 3 — deprecated alias for :max_attempts_per_challenge. Retained
|
|
237
|
-
# for backwards compatibility; new installs should set the newer key.
|
|
238
|
-
# c.passwordless.max_attempts = 3
|
|
239
|
-
|
|
240
|
-
# Default: 30 — OTP-resend cooldown: minimum seconds before the same target
|
|
241
|
-
# can request another code. Enforced in the passwordless start path; a resend
|
|
242
|
-
# inside the window is rejected (the already-sent code stays valid). Set to 0
|
|
243
|
-
# to disable the cooldown.
|
|
244
|
-
# c.passwordless.retry_delay = 30
|
|
245
|
-
|
|
246
|
-
# Custom username validator; return nil/false to accept, or an error message
|
|
247
|
-
# string to reject. Useful for domain allow-lists or truemail integration.
|
|
248
|
-
# Default: nil
|
|
249
|
-
# c.passwordless.username_validator = ->(username, connection_type) {
|
|
250
|
-
# "Invalid email" unless TruemailValidator.valid?(username)
|
|
251
|
-
# }
|
|
252
|
-
|
|
253
|
-
# Custom account factory invoked during passwordless registration.
|
|
254
|
-
# Receives (identifier:, params:, request:) and must return an Account.
|
|
255
|
-
# Default: nil (uses built-in find_or_create logic)
|
|
256
|
-
# c.passwordless.account_factory = ->(identifier:, params:, request:) {
|
|
257
|
-
# Account.create!(email: identifier.value)
|
|
258
|
-
# }
|
|
259
|
-
|
|
260
|
-
# With c.passwordless.delivery = :custom, deliver codes from an event
|
|
261
|
-
# subscriber (c.passwordless_email_sender / _sms_sender were removed in
|
|
262
|
-
# 0.43). It runs synchronously in the request, so I18n.locale etc. are still
|
|
263
|
-
# available. The same subscriber delivers Otp.issue(delivery: :custom) codes.
|
|
264
|
-
#
|
|
265
|
-
# StandardId::Events.subscribe(StandardId::Events::PASSWORDLESS_CODE_GENERATED) do |event|
|
|
266
|
-
# next if event[:skip_sender] # Otp.issue(delivery: :manual)
|
|
267
|
-
# case event[:channel]
|
|
268
|
-
# when "email" then PasswordlessMailer.with(code: event[:code_challenge].code, to: event[:identifier]).deliver_later
|
|
269
|
-
# when "sms" then SmsProvider.send_code(phone: event[:identifier], code: event[:code_challenge].code)
|
|
270
|
-
# end
|
|
271
|
-
# end
|
|
272
|
-
|
|
273
|
-
# ---------------------------------------------------------------------------
|
|
274
|
-
# API engine — OAuth / JWT
|
|
275
|
-
# ---------------------------------------------------------------------------
|
|
276
|
-
|
|
277
|
-
# Default: 3600 — access token lifetime in seconds (1 hour).
|
|
278
|
-
# c.oauth.default_token_lifetime = 3600
|
|
279
|
-
|
|
280
|
-
# Default: 2_592_000 — refresh token lifetime in seconds (30 days).
|
|
281
|
-
# c.oauth.refresh_token_lifetime = 2_592_000
|
|
282
|
-
|
|
283
|
-
# Per-grant overrides. Keys map to OAuth grant types.
|
|
284
|
-
# Default: {}
|
|
285
|
-
# c.oauth.token_lifetimes = {
|
|
286
|
-
# password: 8.hours,
|
|
287
|
-
# client_credentials: 24.hours,
|
|
288
|
-
# social: 24.hours
|
|
289
|
-
# }
|
|
290
|
-
|
|
291
|
-
# Audiences accepted in JWT "aud" claim. An empty array disables audience
|
|
292
|
-
# validation entirely — set this in production.
|
|
293
|
-
# Default: []
|
|
294
|
-
# c.oauth.allowed_audiences = %w[web mobile admin]
|
|
295
|
-
|
|
296
|
-
# Scope-to-claims mapping — when a scope is granted, every listed claim is
|
|
297
|
-
# resolved via claim_resolvers below and embedded in the access token.
|
|
298
|
-
# Default: {}
|
|
299
|
-
# c.oauth.scope_claims = {
|
|
300
|
-
# profile: %i[email display_name]
|
|
301
|
-
# }
|
|
302
|
-
|
|
303
|
-
# Claim resolvers: one per claim listed in scope_claims. Each is a callable
|
|
304
|
-
# receiving keyword args — pick only the ones you need.
|
|
305
|
-
# Available kwargs: account:, client:, request:, audience:, session:
|
|
306
|
-
# Default: {}
|
|
307
|
-
# c.oauth.claim_resolvers = {
|
|
308
|
-
# email: ->(account:) { account.email },
|
|
309
|
-
# display_name: ->(account:, client:) { "#{account.name} for #{client.client_id}" },
|
|
310
|
-
# channel_id: ->(client:) { client.owner_type == "Channel" ? client.owner_id : nil }
|
|
311
|
-
# }
|
|
312
|
-
# c.oauth.allowed_audiences = %w[web mobile admin] # Empty = no validation
|
|
313
|
-
#
|
|
314
|
-
# Audience → profile-type binding. When set, StandardId::AudienceVerification
|
|
315
|
-
# will additionally verify that the authenticated account holds a profile of
|
|
316
|
-
# the configured type for the matched audience. Values may be a single
|
|
317
|
-
# profile-type string or an Array<String> for multi-type audiences.
|
|
318
|
-
# c.oauth.audience_profile_types = {
|
|
319
|
-
# "admin_kit" => "PlatformProfile",
|
|
320
|
-
# "companion_kit" => "DeviceUserProfile",
|
|
321
|
-
# "harness" => ["PlatformProfile", "DeviceUserProfile"]
|
|
322
|
-
# }
|
|
323
|
-
#
|
|
324
|
-
# Optional resolver for picking the profile used for a given audience. Called
|
|
325
|
-
# with (account:, audience:, profile_types:). When nil (default), the gem
|
|
326
|
-
# looks up `account.profiles` and picks one whose `profileable_type` is in
|
|
327
|
-
# the configured profile_types (preferring an `active?` record).
|
|
328
|
-
# c.oauth.audience_profile_resolver = ->(account:, audience:, profile_types:) {
|
|
329
|
-
# account.profiles.active.find_by(profileable_type: profile_types)
|
|
330
|
-
# }
|
|
331
|
-
#
|
|
332
|
-
# Custom claims added to every access token (independent of scopes).
|
|
333
|
-
# Receives keyword arguments: account:, client:, request:, audience:
|
|
334
|
-
# Must return a Hash. Reserved JWT keys (sub, exp, iat, etc.) are excluded.
|
|
335
|
-
# c.oauth.custom_claims = ->(account:, **) { { channel_id: account.channel_id } }
|
|
336
|
-
|
|
337
|
-
# RFC 7662 token introspection (POST /oauth/introspect).
|
|
338
|
-
#
|
|
339
|
-
# Off by default: the endpoint returns 404 and `introspection_endpoint` is not
|
|
340
|
-
# advertised in the discovery documents. An endpoint that answers questions
|
|
341
|
-
# about other people's tokens is not something to expose by accident.
|
|
342
|
-
#
|
|
343
|
-
# Confidential clients only — the caller presents client_id + client_secret
|
|
344
|
-
# (HTTP Basic or form body). Every failure renders `{"active": false}` with no
|
|
345
|
-
# other members, per RFC 7662 §2.2, including a tripped rate limit (a 429
|
|
346
|
-
# would distinguish "throttled" from "token invalid" and make the limiter a
|
|
347
|
-
# token-validity oracle).
|
|
348
|
-
#
|
|
349
|
-
# KNOW THE LIMIT BEFORE BUILDING AN AUTHORIZATION GATE ON THIS. Access tokens
|
|
350
|
-
# are stateless — never persisted, and carrying no `sid` — so a revoked
|
|
351
|
-
# session's access token introspects as `active: true` until its `exp`.
|
|
352
|
-
# Introspection answers "did we mint this, and is it unexpired?", NOT "is it
|
|
353
|
-
# still honoured?". The mitigation is a short access-token lifetime. Refresh
|
|
354
|
-
# tokens ARE persisted and are checked against the row, so a revoked refresh
|
|
355
|
-
# token introspects as inactive immediately.
|
|
356
|
-
#
|
|
357
|
-
# Default: false
|
|
358
|
-
# c.oauth.introspection_enabled = true
|
|
359
|
-
|
|
360
|
-
# JWT signing configuration.
|
|
361
|
-
#
|
|
362
|
-
# Default: :hs256 symmetric signing with Rails.application.secret_key_base.
|
|
363
|
-
# For asymmetric signing, set signing_key and expose the public key via the
|
|
364
|
-
# JWKS endpoint at /.well-known/jwks.json.
|
|
365
|
-
#
|
|
366
|
-
# Algorithms: :hs256/:hs384/:hs512 (HMAC), :rs256/:rs384/:rs512 (RSA),
|
|
367
|
-
# :es256/:es384/:es512 (ECDSA — recommended for new projects).
|
|
368
|
-
#
|
|
369
|
-
# Generate keys:
|
|
370
|
-
# ES256: openssl ecparam -name prime256v1 -genkey -noout -out signing_key.pem
|
|
371
|
-
# RS256: openssl genrsa -out signing_key.pem 2048
|
|
372
|
-
#
|
|
373
|
-
# c.oauth.signing_algorithm = :es256
|
|
374
|
-
# c.oauth.signing_key = Rails.application.credentials.dig(:standard_id, :signing_key)
|
|
375
|
-
#
|
|
376
|
-
# Key rotation — move the previous signing_key here so tokens issued under
|
|
377
|
-
# it still verify during the grace period. Remove once the longest token
|
|
378
|
-
# lifetime has elapsed.
|
|
379
|
-
# c.oauth.previous_signing_keys = [
|
|
380
|
-
# Rails.application.credentials.dig(:standard_id, :previous_signing_key)
|
|
381
|
-
# ]
|
|
382
|
-
# # Cross-algorithm rotation:
|
|
383
|
-
# c.oauth.previous_signing_keys = [
|
|
384
|
-
# { key: Rails.application.credentials.dig(:standard_id, :old_rsa_key), algorithm: :rs256 }
|
|
385
|
-
# ]
|
|
386
|
-
|
|
387
|
-
# ---------------------------------------------------------------------------
|
|
388
|
-
# Lifecycle hooks (WebEngine, synchronous)
|
|
389
|
-
# ---------------------------------------------------------------------------
|
|
390
|
-
|
|
391
|
-
# Called after credential verification, BEFORE session creation.
|
|
392
|
-
# Receives: (account, request, context)
|
|
393
|
-
# context = { mechanism:, provider:, first_sign_in: }
|
|
394
|
-
# Return nil/truthy to proceed, or { error: "message" } to reject.
|
|
395
|
-
# Default: nil
|
|
396
|
-
# c.before_sign_in = ->(account, request, context) {
|
|
397
|
-
# { error: "Access restricted" } unless account.allowed?
|
|
398
|
-
# }
|
|
399
|
-
|
|
400
|
-
# Called after successful sign-in, before redirect.
|
|
401
|
-
# Receives: (account, request, context)
|
|
402
|
-
# context = { first_sign_in:, mechanism:, provider:, session: }
|
|
403
|
-
# Return nil (use default redirect) or a path string to override.
|
|
404
|
-
# Raise StandardId::AuthenticationDenied to reject after sign-in.
|
|
405
|
-
# Default: nil
|
|
406
|
-
# c.after_sign_in = ->(account, request, context) {
|
|
407
|
-
# context[:first_sign_in] ? "/onboarding" : nil
|
|
408
|
-
# }
|
|
409
|
-
|
|
410
|
-
# Decides which authentication methods may sign an account in. Consulted in
|
|
411
|
-
# EVERY web and API flow that establishes a new authentication, after the
|
|
412
|
-
# credential is proven and before any session or token is created.
|
|
413
|
-
# Receives any subset of: account:, auth_method: (:password, :passwordless,
|
|
414
|
-
# :social, :remember_me, :unspecified), provider:, request:, flow:
|
|
415
|
-
# Return truthy to allow, false/nil to refuse, or raise
|
|
416
|
-
# StandardId::LoginMethodDenied.new("message") to refuse with a message.
|
|
417
|
-
# Default: nil (every method allowed)
|
|
418
|
-
# c.login_method_policy = ->(account:, auth_method:, provider:) {
|
|
419
|
-
# next true unless account.staff?
|
|
420
|
-
# auth_method == :social && provider == "void_which_binds"
|
|
421
|
-
# }
|
|
422
|
-
|
|
423
|
-
# Called after a new account is created via any mechanism.
|
|
424
|
-
# Receives: (account, request, context)
|
|
425
|
-
# context = { mechanism:, provider: }
|
|
426
|
-
# Use for profile provisioning, welcome emails, analytics, etc.
|
|
427
|
-
# Default: nil
|
|
428
|
-
# c.after_account_created = ->(account, request, context) {
|
|
429
|
-
# UserProfileProvisioning.call(account, source: context[:mechanism])
|
|
430
|
-
# }
|
|
431
|
-
|
|
432
|
-
# ---------------------------------------------------------------------------
|
|
433
|
-
# Events
|
|
434
|
-
# ---------------------------------------------------------------------------
|
|
435
|
-
|
|
436
|
-
# Emit internal lifecycle events through Rails.logger.
|
|
437
|
-
# Default: false
|
|
438
|
-
# c.events.enable_logging = false
|
|
439
|
-
|
|
440
|
-
# For audit logging, use the standard_audit gem — see the README.
|
|
441
|
-
|
|
442
|
-
# ---------------------------------------------------------------------------
|
|
443
|
-
# Social login
|
|
444
|
-
# ---------------------------------------------------------------------------
|
|
445
|
-
# These fields are declared by the PROVIDER PLUGIN GEMS, not by standard_id
|
|
446
|
-
# itself, so each line below requires the matching gem in your Gemfile:
|
|
447
|
-
# `standard_id-google` for the google_* fields, `standard_id-apple` for the
|
|
448
|
-
# apple_* fields. Uncommenting a line without its gem raises
|
|
449
|
-
# StandardId::ConfigurationError ("Unknown field ... for scope 'social'") —
|
|
450
|
-
# the field genuinely does not exist, because nothing declared it.
|
|
451
|
-
#
|
|
452
|
-
# With the gem present, writing them here — in a plain initializer, in the
|
|
453
|
-
# `social` scope, exactly as shown — is correct and supported. standard_id
|
|
454
|
-
# >= 0.33.0 declares every loaded provider's fields before initializers run.
|
|
455
|
-
# On 0.32.0 and earlier these same lines raised, because the fields were only
|
|
456
|
-
# declared later from the plugin's Railtie, and apps worked around it by
|
|
457
|
-
# wrapping the writes in `Rails.application.config.after_initialize`. That
|
|
458
|
-
# wrapper is no longer needed, and it still works if you have one.
|
|
459
|
-
#
|
|
460
|
-
# ENV defaults (standard_id >= 0.42): every provider field you do not assign
|
|
461
|
-
# falls back to the ENV variable named after it, upper-cased — so with the
|
|
462
|
-
# canonical variables below set, you need none of these lines. Assign a
|
|
463
|
-
# field only to read it from somewhere else (a differently named variable,
|
|
464
|
-
# Rails credentials); an explicit assignment, even of nil, always wins.
|
|
465
|
-
#
|
|
466
|
-
# GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
|
|
467
|
-
# APPLE_CLIENT_ID, APPLE_MOBILE_CLIENT_ID,
|
|
468
|
-
# APPLE_PRIVATE_KEY, APPLE_KEY_ID, APPLE_TEAM_ID
|
|
469
|
-
#
|
|
470
|
-
# A provider is enabled when its client ID is present
|
|
471
|
-
# (StandardId.social_provider_enabled?(:google)). An enabled provider missing
|
|
472
|
-
# fields its plugin marks required is reported at boot — see
|
|
473
|
-
# c.social.provider_misconfiguration below.
|
|
474
|
-
|
|
475
|
-
# c.social.google_client_id = Rails.application.credentials.dig(:google, :client_id)
|
|
476
|
-
# c.social.google_client_secret = Rails.application.credentials.dig(:google, :client_secret)
|
|
477
|
-
# c.social.apple_client_id = ENV["APPLE_CLIENT_ID"] # web (Services ID) flow
|
|
478
|
-
# c.social.apple_mobile_client_id = ENV["APPLE_MOBILE_CLIENT_ID"] # native id_token flow
|
|
479
|
-
# c.social.apple_private_key = ENV["APPLE_PRIVATE_KEY"] # .p8 PEM, newlines intact
|
|
480
|
-
# c.social.apple_key_id = ENV["APPLE_KEY_ID"]
|
|
481
|
-
# c.social.apple_team_id = ENV["APPLE_TEAM_ID"]
|
|
482
|
-
|
|
483
|
-
# What to do at boot when an enabled provider is missing required fields
|
|
484
|
-
# (e.g. APPLE_CLIENT_ID set without APPLE_PRIVATE_KEY — the sign-in flow
|
|
485
|
-
# would start, then fail at the callback). :warn logs a warning; :raise
|
|
486
|
-
# raises StandardId::ConfigurationError in production and warns elsewhere.
|
|
487
|
-
# Default: :warn
|
|
488
|
-
# c.social.provider_misconfiguration = :raise
|
|
489
|
-
|
|
490
|
-
# Mobile redirect URI allow-list — custom schemes used by native apps.
|
|
491
|
-
# Default: []
|
|
492
|
-
# c.social.allowed_redirect_url_prefixes = ["example-app://"]
|
|
493
|
-
|
|
494
|
-
# OAuth scopes the host app supports for social providers.
|
|
495
|
-
# Default: []
|
|
496
|
-
# c.social.available_scopes = %w[profile email offline_access]
|
|
497
|
-
|
|
498
|
-
# Account linking strategy, for a social login whose (provider, sub) is not
|
|
499
|
-
# linked yet but whose email belongs to an existing account:
|
|
500
|
-
# :strict — link only if that email identifier came from the same
|
|
501
|
-
# provider, predates provider tracking, or the account
|
|
502
|
-
# already has an identifier from this provider (default)
|
|
503
|
-
# :trust_provider — link to the account that holds the email
|
|
504
|
-
# Under both, the provider must report the email as verified, and an email
|
|
505
|
-
# already linked to a different sub from the same provider is refused.
|
|
506
|
-
# Default: :strict
|
|
507
|
-
# c.social.link_strategy = :trust_provider
|
|
508
|
-
|
|
509
|
-
# Attributes written to the Account on social signup.
|
|
510
|
-
# Default: nil
|
|
511
|
-
# c.social.social_account_attributes = ->(social_info:, provider:) {
|
|
512
|
-
# { email: social_info[:email], name: social_info[:name] || social_info[:given_name] }
|
|
513
|
-
# }
|
|
514
|
-
|
|
515
|
-
# ---------------------------------------------------------------------------
|
|
516
|
-
# OIDC logout allow-list
|
|
517
|
-
# ---------------------------------------------------------------------------
|
|
518
|
-
|
|
519
|
-
# Valid post_logout_redirect_uri values accepted by the OIDC RP-initiated
|
|
520
|
-
# logout endpoint.
|
|
521
|
-
# Default: []
|
|
522
|
-
# c.allowed_post_logout_redirect_uris = [
|
|
523
|
-
# "https://app.example.com/logged_out",
|
|
524
|
-
# "https://admin.example.com/signout"
|
|
525
|
-
# ]
|
|
526
|
-
|
|
527
|
-
# ---------------------------------------------------------------------------
|
|
528
|
-
# Rate limits (Rails 8 rate_limit DSL)
|
|
529
|
-
# ---------------------------------------------------------------------------
|
|
530
|
-
# Defaults are conservative; tune per environment.
|
|
531
|
-
|
|
532
|
-
# Login limits. The login action branches password OR passwordless, so on a
|
|
533
|
-
# passwordless app these govern the OTP-SEND limit. Prefer the
|
|
534
|
-
# mechanism-agnostic names (password_login_per_ip/_per_email were removed in 0.43).
|
|
535
|
-
# c.rate_limits.login_per_ip = 20 # per 15 minutes
|
|
536
|
-
# c.rate_limits.login_per_email = 5 # per 15 minutes
|
|
537
|
-
# c.rate_limits.otp_verify_per_ip = 20 # per 15 minutes
|
|
538
|
-
# c.rate_limits.verification_start_per_target = 3 # per 15 minutes
|
|
539
|
-
# c.rate_limits.verification_start_per_ip = 10 # per hour
|
|
540
|
-
# c.rate_limits.api_passwordless_start_per_ip = 10 # per hour
|
|
541
|
-
# c.rate_limits.api_passwordless_start_per_target = 5 # per 15 minutes
|
|
542
|
-
# c.rate_limits.api_token_per_ip = 30 # per 15 minutes
|
|
543
|
-
|
|
544
|
-
# Token introspection (only relevant with c.oauth.introspection_enabled).
|
|
545
|
-
# Throttles by IP so the endpoint can't be used to bulk-classify stolen
|
|
546
|
-
# tokens. Note the limit renders the ordinary {"active": false} / 200 rather
|
|
547
|
-
# than 429 — see the introspection_enabled comment above for why.
|
|
548
|
-
# c.rate_limits.introspection_per_ip = 30 # per 15 minutes
|
|
549
|
-
|
|
550
|
-
# Optional per-audience tightening on top of api_token_per_ip. Only token
|
|
551
|
-
# requests targeting a configured audience count toward its cap (per IP,
|
|
552
|
-
# per 15 minutes); unlisted audiences are governed by the global ceiling.
|
|
553
|
-
# c.rate_limits.api_token_per_audience_per_ip = { "mobile_app" => 10 }
|
|
554
|
-
|
|
555
|
-
# ---------------------------------------------------------------------------
|
|
556
|
-
# Observability
|
|
557
|
-
# ---------------------------------------------------------------------------
|
|
558
|
-
|
|
559
|
-
# Extra fields merged into Sentry user context for authenticated requests.
|
|
560
|
-
# Receives (account, session). Return a Hash; non-callable values ignored.
|
|
561
|
-
# Default: nil
|
|
562
|
-
# c.sentry_context = ->(account, _session) {
|
|
563
|
-
# { segment: account.primary_profile&.type }
|
|
564
|
-
# }
|
|
565
26
|
end
|
|
566
|
-
|
|
567
|
-
# Notes:
|
|
568
|
-
# - All scopes/fields are defined via the schema DSL in
|
|
569
|
-
# lib/standard_id/config/schema.rb
|
|
570
|
-
# - Values are cast to the declared type (string, integer, boolean, ...).
|
|
571
|
-
# - Invalid scopes or fields raise ArgumentError with a helpful message.
|