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