standard_id 0.52.0 → 0.54.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 +61 -4
- data/README.md +81 -5
- data/app/controllers/concerns/standard_id/passwordless_flow.rb +65 -1
- data/app/controllers/standard_id/web/login_controller.rb +4 -13
- data/app/controllers/standard_id/web/login_verify_controller.rb +6 -15
- data/app/mailers/standard_id/application_mailer.rb +10 -3
- data/lib/generators/standard_id/install/install_generator.rb +4 -1
- data/lib/generators/standard_id/install/templates/standard_id.rb +11 -563
- data/lib/generators/standard_id/install/templates/standard_id_reference.rb +586 -0
- data/lib/standard_id/app_environment.rb +26 -0
- data/lib/standard_id/config/schema.rb +14 -9
- 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 +16 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de8ced4a8e48a3b14437ece4082079510d1f7466737be90f3dffe3ac3959d56c
|
|
4
|
+
data.tar.gz: af90eba1873dd457c207ce3151d75215cf5de2e8a5cb37d42ab32a1227fb5017
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d5bb7b2e920c26ae28e3b8ddd09ea4c475bb8b0562b89f057adf9573737fc225c43470faf94e202545d3840a4ab2b41e6abc8b17836fd8272a6b17711e47d5f6
|
|
7
|
+
data.tar.gz: b64139dbd6641e978e75d4dec5f89a14891b603755edf6e67e2a566c5ace604d678aaeb4f75a6e924f1da2d04fdd5af3de8b0f005c05c344486aa2796d45b096
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.54.0] - 2026-10-22
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`PasswordlessFlow#start_passwordless_login(username:, connection:, return_to:)`.**
|
|
15
|
+
Sends the code and stores the signed hand-off the WebEngine's `login_verify` page
|
|
16
|
+
requires, so a host controller (a sign-up or invite form) can start a passwordless
|
|
17
|
+
login and redirect to `standard_id_web.login_verify_path` without copying the
|
|
18
|
+
gem's `message_verifier(:otp)` / `session[:standard_id_otp_payload]` internals.
|
|
19
|
+
Nothing is stored when the code cannot be sent. The gem's own login controllers
|
|
20
|
+
now use the same code. (rarebit-one/rarebit-sre#479)
|
|
21
|
+
- **`c.mailer_parent_class`** (default `"ActionMailer::Base"`). The class
|
|
22
|
+
StandardId's mailers inherit from; set it to `"ApplicationMailer"` so built-in OTP
|
|
23
|
+
and password-reset mail uses the host's layout, helpers, `default_url_options`
|
|
24
|
+
and locale handling. With a host parent the gem no longer sets its placeholder
|
|
25
|
+
`default from:` or `layout "mailer"`, so the parent's apply. A value that is
|
|
26
|
+
not an `ActionMailer::Base` subclass raises `StandardId::ConfigurationError`
|
|
27
|
+
naming the setting.
|
|
28
|
+
(rarebit-one/rarebit-sre#479)
|
|
29
|
+
|
|
30
|
+
## [0.53.0] - 2026-10-22
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`StandardId::Testing::RequestHelpers#sign_in_as(account, scope: nil)`.** Signs an
|
|
35
|
+
account in for the following requests in a request spec: it creates a real
|
|
36
|
+
`BrowserSession` and writes the encrypted `session_token` cookie a real sign-in
|
|
37
|
+
writes, built with the host's own cookie jar, so no test-only route is added to
|
|
38
|
+
the app. `scope:` also writes `standard_id_scopes` into the Rails session cookie
|
|
39
|
+
(cookie session store only; any other store raises `ArgumentError`, before
|
|
40
|
+
anything is created). Like a real sign-in it always replaces the Rails session
|
|
41
|
+
cookie, so a later `sign_in_as` never inherits an earlier one's scopes or any
|
|
42
|
+
other session data (flash, CSRF token). Replaces per-app controller
|
|
43
|
+
stubs and `/test/set-session` routes.
|
|
44
|
+
- **`RequestHelpers#bearer_headers_for(account, **jwt_options)`:** `build_jwt` plus
|
|
45
|
+
`bearer_auth_header` in one call.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- **"Production" follows `APP_ENVIRONMENT`, failing closed.** The new
|
|
50
|
+
`StandardId::AppEnvironment.production?` decides every production check in the
|
|
51
|
+
gem: the bypass-code guard (when `passwordless.production_env_detector` is
|
|
52
|
+
unset), the provider-misconfiguration `:raise` mode, and the empty
|
|
53
|
+
`allowed_audiences` boot warning. It reads `APP_ENVIRONMENT` when set (only
|
|
54
|
+
`staging`, `preview`, `development` and `test` are non-production; any other
|
|
55
|
+
value is production), else `Rails.env.production?`. A staging box with
|
|
56
|
+
`RAILS_ENV=production` and `APP_ENVIRONMENT=staging` is therefore no longer
|
|
57
|
+
treated as production, and `-> { AppEnv.production? }` detectors can be
|
|
58
|
+
deleted. Same convention as standard_audit, standard_health and
|
|
59
|
+
standard_singpass.
|
|
60
|
+
- **`rails g standard_id:install` writes a minimal initializer.** It holds only
|
|
61
|
+
`account_class_name`, plus commented `issuer` and `c.preset :inertia_web` lines,
|
|
62
|
+
instead of several hundred lines of commented defaults that apps then kept
|
|
63
|
+
verbatim. `--with-reference` writes
|
|
64
|
+
the annotated initializer as before. Existing initializers are not touched.
|
|
65
|
+
|
|
10
66
|
## [0.52.0] - 2026-10-22
|
|
11
67
|
|
|
12
68
|
### Added
|
|
@@ -14,10 +70,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
70
|
- **`c.preset :inertia_web`.** Applies the settings an Inertia app with
|
|
15
71
|
passwordless or social sign-in flips: `use_inertia` and `alias_current_user`
|
|
16
72
|
true; `web.signup`, `web.password_login` and `web.password_reset` false;
|
|
17
|
-
`oauth.discovery_endpoint_base = :request`.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
73
|
+
`oauth.discovery_endpoint_base = :request`. Each setting is hand-set in four
|
|
74
|
+
of the five estate apps, though the full bundle matches only two of them. A
|
|
75
|
+
preset fills only settings the host has not assigned (via
|
|
76
|
+
`Scope#assigned?`), so explicit assignments win in either order; an unknown
|
|
77
|
+
name raises `ArgumentError`. The public defaults are unchanged.
|
|
21
78
|
Presets live in `StandardId::Config::Presets`.
|
|
22
79
|
|
|
23
80
|
## [0.51.2] - 2026-10-21
|
data/README.md
CHANGED
|
@@ -63,6 +63,13 @@ $ bundle install
|
|
|
63
63
|
rails generate standard_id:install
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
This writes a short `config/initializers/standard_id.rb` holding only what an
|
|
67
|
+
app must decide (`account_class_name`, plus commented `issuer` and
|
|
68
|
+
`c.preset :inertia_web` lines). Add a setting only when you change it from the
|
|
69
|
+
default. `--with-reference` writes the annotated initializer instead, with every
|
|
70
|
+
commonly changed setting and its default commented out; it is a reference to
|
|
71
|
+
read, not a file to keep whole.
|
|
72
|
+
|
|
66
73
|
### 2. Configure Your Account Model
|
|
67
74
|
|
|
68
75
|
```ruby
|
|
@@ -810,6 +817,14 @@ locale files, or the templates in `app/views/standard_id/passwordless_mailer/`.
|
|
|
810
817
|
An assigned `c.passwordless.mailer_subject` still sets the sign-in subject. The subscriber runs synchronously inside the request, so
|
|
811
818
|
`I18n.locale`, `Current.*` and the like are still available.
|
|
812
819
|
|
|
820
|
+
The built-in mailers inherit from `ActionMailer::Base`. Set
|
|
821
|
+
`c.mailer_parent_class = "ApplicationMailer"` to have them inherit your mailer
|
|
822
|
+
base instead, with its layout, helpers, `default_url_options` and locale
|
|
823
|
+
handling. It is read when the gem's mailers load, so set it in an initializer.
|
|
824
|
+
The sender still comes from `c.passwordless.mailer_from` and
|
|
825
|
+
`c.reset_password.mailer_from`, which every gem message passes explicitly, so a
|
|
826
|
+
parent's `default from:` does not apply to them.
|
|
827
|
+
|
|
813
828
|
Event payload includes:
|
|
814
829
|
- `channel` - `"email"` or `"sms"`
|
|
815
830
|
- `identifier` - The email address or phone number
|
|
@@ -821,6 +836,31 @@ Event payload includes:
|
|
|
821
836
|
|
|
822
837
|
> **Note**: `passwordless_email_sender` / `passwordless_sms_sender` were removed in 0.43; see the [Migration Guide](docs/MIGRATION_GUIDE.md).
|
|
823
838
|
|
|
839
|
+
### Starting a passwordless login from your own controller
|
|
840
|
+
|
|
841
|
+
To send the code from your own controller (a sign-up form, an invite page) and
|
|
842
|
+
let the WebEngine's verify page finish the sign-in, include
|
|
843
|
+
`StandardId::PasswordlessFlow` and call `start_passwordless_login`. It sends the
|
|
844
|
+
code and stores the signed hand-off the verify page requires, then you redirect
|
|
845
|
+
there through the engine's mounted helper, which includes the mount path:
|
|
846
|
+
|
|
847
|
+
```ruby
|
|
848
|
+
class Onboarding::SignupsController < ApplicationController
|
|
849
|
+
include StandardId::PasswordlessFlow
|
|
850
|
+
|
|
851
|
+
def create
|
|
852
|
+
start_passwordless_login(username: params[:email], return_to: welcome_path)
|
|
853
|
+
redirect_to standard_id_web.login_verify_path, status: :see_other
|
|
854
|
+
rescue StandardId::InvalidRequestError => e
|
|
855
|
+
redirect_to new_signup_path, alert: e.message
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
`connection:` defaults to `c.passwordless.connection`. `return_to:` becomes the
|
|
861
|
+
post-sign-in destination, still subject to the verify page's safe-redirect check.
|
|
862
|
+
Nothing is stored when the code cannot be sent.
|
|
863
|
+
|
|
824
864
|
### Using OTP for non-authentication flows
|
|
825
865
|
|
|
826
866
|
The same hardened OTP machinery (enumeration defense, atomic attempt tracking, pessimistic locking, bypass_code hook) is exposed as a public primitive via `StandardId::Otp`. Use it when you need one-time codes for purposes *other* than authentication — contact verification widgets, step-up challenges, custom confirmation flows, etc.
|
|
@@ -869,15 +909,19 @@ end
|
|
|
869
909
|
|
|
870
910
|
**Bypass code (E2E testing).** When `StandardId.config.passwordless.bypass_code` is set (and the deploy is not production), `Otp.verify` accepts the bypass code for **any realm**. This replaces per-app bypass ENV checks and works consistently across `Otp.verify` and the built-in passwordless login flow. Never set `bypass_code` in production — it will raise if you try.
|
|
871
911
|
|
|
872
|
-
|
|
912
|
+
"Production" is decided by `StandardId::AppEnvironment`, the same rule every
|
|
913
|
+
check in the gem uses: `APP_ENVIRONMENT` when it is set, where only `staging`,
|
|
914
|
+
`preview`, `development` and `test` count as non-production (any other value,
|
|
915
|
+
including a typo, is production), else `Rails.env.production?`. So a staging box
|
|
916
|
+
running `RAILS_ENV=production` with `APP_ENVIRONMENT=staging` is not
|
|
917
|
+
production, and needs no extra wiring:
|
|
873
918
|
|
|
874
919
|
```ruby
|
|
875
|
-
|
|
876
|
-
c.passwordless.production_env_detector = -> { AppEnv.production? }
|
|
877
|
-
c.passwordless.bypass_code = ENV["STANDARD_ID_BYPASS_CODE"] unless AppEnv.production?
|
|
920
|
+
c.passwordless.bypass_code = ENV["STANDARD_ID_BYPASS_CODE"] unless StandardId::AppEnvironment.production?
|
|
878
921
|
```
|
|
879
922
|
|
|
880
|
-
`production_env_detector`
|
|
923
|
+
`production_env_detector` (a callable taking no arguments, returning a boolean)
|
|
924
|
+
still overrides that, for an app with a different discriminator.
|
|
881
925
|
|
|
882
926
|
**Back-compat.** The existing passwordless authentication flow continues to work unchanged. `Otp.issue`/`Otp.verify` are a new addition — you can migrate direct `CodeChallenge.create!` calls at your own pace.
|
|
883
927
|
|
|
@@ -1943,6 +1987,38 @@ Notes:
|
|
|
1943
1987
|
|
|
1944
1988
|
## Testing
|
|
1945
1989
|
|
|
1990
|
+
### Signing in from your app's request specs
|
|
1991
|
+
|
|
1992
|
+
```ruby
|
|
1993
|
+
# spec/rails_helper.rb
|
|
1994
|
+
require "standard_id/testing"
|
|
1995
|
+
|
|
1996
|
+
RSpec.configure do |config|
|
|
1997
|
+
config.include StandardId::Testing::RequestHelpers, type: :request
|
|
1998
|
+
end
|
|
1999
|
+
```
|
|
2000
|
+
|
|
2001
|
+
```ruby
|
|
2002
|
+
sign_in_as(account) # a real BrowserSession, via the session_token cookie
|
|
2003
|
+
sign_in_as(account, scope: :platform) # also grants standard_id_scopes (current_scope_names)
|
|
2004
|
+
get "/dashboard"
|
|
2005
|
+
|
|
2006
|
+
get "/api/orders", headers: bearer_headers_for(account, scope: "orders.read")
|
|
2007
|
+
```
|
|
2008
|
+
|
|
2009
|
+
`sign_in_as` creates a real `BrowserSession` and writes the same encrypted
|
|
2010
|
+
`session_token` cookie a real sign-in writes, so `current_account`,
|
|
2011
|
+
`require_browser_session!`, expiry and revocation behave exactly as in
|
|
2012
|
+
production. It adds nothing to your app: the cookies are built with your app's
|
|
2013
|
+
own encrypted cookie jar, so there is no test-only route. `scope:` writes the
|
|
2014
|
+
Rails session cookie and so needs the cookie session store (the Rails default);
|
|
2015
|
+
with another store it raises `ArgumentError`, and you sign in through your own
|
|
2016
|
+
scoped login request instead. It replaces controller stubbing
|
|
2017
|
+
(`stub_web_authentication`, `allow_any_instance_of(...).to receive(:current_user)`)
|
|
2018
|
+
and per-app `/test/set-session` routes.
|
|
2019
|
+
|
|
2020
|
+
### This gem's own suite
|
|
2021
|
+
|
|
1946
2022
|
StandardId includes comprehensive test coverage:
|
|
1947
2023
|
|
|
1948
2024
|
```bash
|
|
@@ -6,7 +6,8 @@ module StandardId
|
|
|
6
6
|
#
|
|
7
7
|
# Requires the including controller to have access to `request` (standard in
|
|
8
8
|
# all Rails controllers). No other dependencies are needed -- both
|
|
9
|
-
# `generate_passwordless_otp` and `verify_passwordless_otp` only use `request
|
|
9
|
+
# `generate_passwordless_otp` and `verify_passwordless_otp` only use `request`;
|
|
10
|
+
# `start_passwordless_login` also writes to `session`.
|
|
10
11
|
#
|
|
11
12
|
# @example Usage in a host app controller
|
|
12
13
|
# class Auth::LoginController < ApplicationController
|
|
@@ -29,13 +30,76 @@ module StandardId
|
|
|
29
30
|
# end
|
|
30
31
|
# end
|
|
31
32
|
# end
|
|
33
|
+
#
|
|
34
|
+
# @example Hand off to the WebEngine's verify page (no custom verify action)
|
|
35
|
+
# class Onboarding::SignupsController < ApplicationController
|
|
36
|
+
# include StandardId::PasswordlessFlow
|
|
37
|
+
#
|
|
38
|
+
# def create
|
|
39
|
+
# start_passwordless_login(username: params[:email], return_to: welcome_path)
|
|
40
|
+
# redirect_to standard_id_web.login_verify_path, status: :see_other
|
|
41
|
+
# rescue StandardId::InvalidRequestError => e
|
|
42
|
+
# redirect_to new_signup_path, alert: e.message
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
32
45
|
module PasswordlessFlow
|
|
33
46
|
extend ActiveSupport::Concern
|
|
34
47
|
|
|
35
48
|
include StandardId::PasswordlessStrategy
|
|
36
49
|
|
|
50
|
+
# Session key for the signed hand-off the WebEngine's login_verify page reads.
|
|
51
|
+
OTP_PAYLOAD_SESSION_KEY = :standard_id_otp_payload
|
|
52
|
+
|
|
37
53
|
private
|
|
38
54
|
|
|
55
|
+
# Start a passwordless login that the WebEngine's login_verify page finishes.
|
|
56
|
+
#
|
|
57
|
+
# Sends the code (as `generate_passwordless_otp`), then stores the signed
|
|
58
|
+
# hand-off that login_verify requires, valid for `passwordless.code_ttl`.
|
|
59
|
+
# The caller redirects to that page itself, through the engine's mounted
|
|
60
|
+
# helper so the mount path is included (e.g. `standard_id_web.login_verify_path`).
|
|
61
|
+
#
|
|
62
|
+
# Nothing is stored when sending fails, so the verify page is never reachable
|
|
63
|
+
# for a code that was not sent.
|
|
64
|
+
#
|
|
65
|
+
# @param username [String] the recipient's email address or phone number
|
|
66
|
+
# @param connection [String] the delivery channel, defaults to `passwordless.connection`
|
|
67
|
+
# @param return_to [String, nil] where to send the account after it verifies;
|
|
68
|
+
# the verify page still checks it is a safe destination. Non-String values are ignored.
|
|
69
|
+
# @return [StandardId::CodeChallenge] the created code challenge
|
|
70
|
+
# @raise [StandardId::InvalidRequestError] when the username format is invalid
|
|
71
|
+
# or the connection type is unsupported
|
|
72
|
+
def start_passwordless_login(username:, connection: StandardId.config.passwordless.connection, return_to: nil)
|
|
73
|
+
challenge = generate_passwordless_otp(username: username, connection: connection)
|
|
74
|
+
store_passwordless_handoff(username: username, connection: connection)
|
|
75
|
+
session[:return_to_after_authenticating] = return_to if return_to.is_a?(String) && return_to.present?
|
|
76
|
+
challenge
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def store_passwordless_handoff(username:, connection:)
|
|
80
|
+
session[OTP_PAYLOAD_SESSION_KEY] = Rails.application.message_verifier(:otp).generate(
|
|
81
|
+
{ username: username, connection: connection },
|
|
82
|
+
expires_in: StandardId.config.passwordless.code_ttl.seconds
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The stored hand-off as `{ username:, connection: }`, or nil when there is
|
|
87
|
+
# none. A tampered or expired hand-off is deleted and raises
|
|
88
|
+
# ActiveSupport::MessageVerifier::InvalidSignature.
|
|
89
|
+
def read_passwordless_handoff
|
|
90
|
+
signed_payload = session[OTP_PAYLOAD_SESSION_KEY]
|
|
91
|
+
return if signed_payload.blank?
|
|
92
|
+
|
|
93
|
+
Rails.application.message_verifier(:otp).verify(signed_payload).symbolize_keys
|
|
94
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
95
|
+
clear_passwordless_handoff
|
|
96
|
+
raise
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def clear_passwordless_handoff
|
|
100
|
+
session.delete(OTP_PAYLOAD_SESSION_KEY)
|
|
101
|
+
end
|
|
102
|
+
|
|
39
103
|
# Generate a passwordless OTP code and send it to the user.
|
|
40
104
|
#
|
|
41
105
|
# Delegates to the appropriate strategy (EmailStrategy or SmsStrategy)
|
|
@@ -100,25 +100,16 @@ module StandardId
|
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
begin
|
|
103
|
-
|
|
103
|
+
# string_param rejects Array/Hash-shaped redirect_uri values (see
|
|
104
|
+
# BaseController#string_param); start_passwordless_login also ignores
|
|
105
|
+
# non-Strings, since login_verify passes the stored value to redirect_to.
|
|
106
|
+
start_passwordless_login(username: username, connection: connection, return_to: string_param(:redirect_uri))
|
|
104
107
|
rescue StandardId::InvalidRequestError => e
|
|
105
108
|
flash.now[:alert] = e.message
|
|
106
109
|
render_with_inertia action: :show, props: auth_page_props(passwordless_enabled: passwordless_enabled?), status: :unprocessable_content
|
|
107
110
|
return
|
|
108
111
|
end
|
|
109
112
|
|
|
110
|
-
code_ttl = StandardId.config.passwordless.code_ttl
|
|
111
|
-
signed_payload = Rails.application.message_verifier(:otp).generate(
|
|
112
|
-
{ username: username, connection: connection },
|
|
113
|
-
expires_in: code_ttl.seconds
|
|
114
|
-
)
|
|
115
|
-
session[:standard_id_otp_payload] = signed_payload
|
|
116
|
-
# Use string_param to reject Array/Hash-shaped values — login_verify_controller
|
|
117
|
-
# consumes this via after_authentication_url and passes it to redirect_to;
|
|
118
|
-
# storing a non-String here would crash that flow with ArgumentError.
|
|
119
|
-
redirect_uri = string_param(:redirect_uri)
|
|
120
|
-
session[:return_to_after_authenticating] = redirect_uri if redirect_uri
|
|
121
|
-
|
|
122
113
|
redirect_to engine_path(login_verify_path), status: :see_other
|
|
123
114
|
end
|
|
124
115
|
|
|
@@ -75,7 +75,7 @@ module StandardId
|
|
|
75
75
|
}
|
|
76
76
|
redirect_override = invoke_after_sign_in(account, context)
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
clear_passwordless_handoff
|
|
79
79
|
|
|
80
80
|
# after_authentication_url returns whatever was stashed in
|
|
81
81
|
# session[:return_to_after_authenticating] — which could be an attacker-controlled
|
|
@@ -85,7 +85,7 @@ module StandardId
|
|
|
85
85
|
destination = redirect_override || (safe_destination?(fallback) ? fallback : safe_post_signin_default)
|
|
86
86
|
redirect_after_authentication destination, notice: "Successfully signed in"
|
|
87
87
|
rescue StandardId::AuthenticationDenied => e
|
|
88
|
-
|
|
88
|
+
clear_passwordless_handoff
|
|
89
89
|
handle_authentication_denied(e, account: account, newly_created: newly_created)
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -96,19 +96,10 @@ module StandardId
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def require_otp_payload!
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
begin
|
|
107
|
-
@otp_data = Rails.application.message_verifier(:otp).verify(signed_payload).symbolize_keys
|
|
108
|
-
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
109
|
-
session.delete(:standard_id_otp_payload)
|
|
110
|
-
redirect_to engine_path(login_path), alert: "Your verification session has expired. Please try again."
|
|
111
|
-
end
|
|
99
|
+
@otp_data = read_passwordless_handoff
|
|
100
|
+
redirect_to engine_path(login_path), alert: "Please start the login process" if @otp_data.nil?
|
|
101
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
102
|
+
redirect_to engine_path(login_path), alert: "Your verification session has expired. Please try again."
|
|
112
103
|
end
|
|
113
104
|
|
|
114
105
|
# Global web.passwordless_registration AND the active scope's
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
module StandardId
|
|
2
|
-
|
|
2
|
+
# Inherits from `StandardId.config.mailer_parent_class` (ActionMailer::Base by
|
|
3
|
+
# default). The class is resolved when this file loads, which is after the
|
|
4
|
+
# host's initializers have run.
|
|
5
|
+
class ApplicationMailer < StandardId.mailer_parent_class
|
|
3
6
|
# Use a delivery job that doesn't log its arguments — the gem's mailers pass
|
|
4
7
|
# sensitive params (OTP code, password-reset token) that ActiveJob would
|
|
5
8
|
# otherwise print in plaintext. Applies to all StandardId mailers.
|
|
6
9
|
self.delivery_job = StandardId::SecureMailDeliveryJob
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
# A host parent brings its own defaults and layout; keep the gem's only on
|
|
12
|
+
# the bare ActionMailer::Base.
|
|
13
|
+
if superclass == ActionMailer::Base
|
|
14
|
+
default from: "from@example.com"
|
|
15
|
+
layout "mailer"
|
|
16
|
+
end
|
|
10
17
|
end
|
|
11
18
|
end
|
|
@@ -28,6 +28,8 @@ module StandardId
|
|
|
28
28
|
|
|
29
29
|
class_option :skip_initializer, type: :boolean, default: false,
|
|
30
30
|
desc: "Do not write config/initializers/standard_id.rb"
|
|
31
|
+
class_option :with_reference, type: :boolean, default: false,
|
|
32
|
+
desc: "Write the annotated initializer (every common setting with its default) instead of the minimal one"
|
|
31
33
|
class_option :skip_routes, type: :boolean, default: false,
|
|
32
34
|
desc: "Do not append engine mount lines to config/routes.rb"
|
|
33
35
|
class_option :skip_migrations, type: :boolean, default: false,
|
|
@@ -51,7 +53,8 @@ module StandardId
|
|
|
51
53
|
def create_initializer_file
|
|
52
54
|
return say_status("skip", "config/initializers/standard_id.rb (--skip-initializer)", :yellow) if options[:skip_initializer]
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
source = options[:with_reference] ? "standard_id_reference.rb" : "standard_id.rb"
|
|
57
|
+
template source, "config/initializers/standard_id.rb"
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def mount_engines
|