standard_id 0.2.9 → 0.3.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: 2452ed8123165d861c0336f59c980b08aad25936f3a1aaee39a2b13f370148b8
4
- data.tar.gz: a1390102deff80b924ac9d6ce5f53e260a0daaf0001524486f909c9d64c422d9
3
+ metadata.gz: 9878cd708e3ea39bc8cad97946dd6509c2423a815ade09c726d80c591f72cf7f
4
+ data.tar.gz: 4ee3cf785da092a4efd199dee423d214c0b99936928cd381e8167650474c9c7b
5
5
  SHA512:
6
- metadata.gz: 598e2740f693cc6e1f58c77f02897fe2f39c6bd2c2748c7f40bd729bafe1f15bc80fccc6755daeabe0a1ca2a08c0bb547b5f308f0596a233dc6e7ca81939c68f
7
- data.tar.gz: eae44ac9c2cb4d0226547519b8d67ff042b4e3395efd0ffb91cb41ba7b9d8789c2f1a6afbfc4e2f75dcb32aad27ec9414ccbe30906b28d83f5bac842c42fa482
6
+ metadata.gz: 68cdd5a479a507d7647055b06534744f458546ac81a0a1bf71b0464739316ade506a50303a811fefac56142f223d1d913af4a41fd53243a1678ec64a50f48d5e
7
+ data.tar.gz: bb8137fb52314263901a3ed6640ef9b327f1397424b68776af4edd4b37be9d0da26e7974d5728252981823b9dd48fc4aec4825f960aec3a3769a20a7cef3ead4
data/README.md CHANGED
@@ -516,27 +516,71 @@ StandardId::Events.subscribe(/social/) do |event|
516
516
  end
517
517
  ```
518
518
 
519
- #### Class-based (complex logic)
519
+ ### Audit Logging
520
+
521
+ For production audit trails, use the [standard_audit](https://github.com/rarebit-one/standard_audit) gem. StandardId and StandardAudit have zero direct references to each other — the host application wires them together.
522
+
523
+ #### Setup
524
+
525
+ Add both gems to your Gemfile:
520
526
 
521
527
  ```ruby
522
- # app/subscribers/audit_subscriber.rb
523
- class AuditSubscriber < StandardId::Events::Subscribers::Base
524
- subscribe_to StandardId::Events::SECURITY_EVENTS
528
+ gem "standard_id"
529
+ gem "standard_audit"
530
+ ```
525
531
 
526
- def call(event)
527
- AuditLog.create!(
528
- event_type: event.short_name,
529
- account_id: event[:account]&.id,
530
- ip_address: event[:ip_address],
531
- metadata: event.payload
532
- )
533
- end
532
+ Run the StandardAudit install generator:
533
+
534
+ ```bash
535
+ rails generate standard_audit:install
536
+ rails db:migrate
537
+ ```
538
+
539
+ #### Wiring StandardId events to StandardAudit
540
+
541
+ Configure StandardAudit to subscribe to StandardId's event namespace and map its payload conventions:
542
+
543
+ ```ruby
544
+ # config/initializers/standard_audit.rb
545
+ StandardAudit.configure do |config|
546
+ config.subscribe_to /\Astandard_id\./
547
+
548
+ # StandardId uses :account and :current_account rather than :actor/:target.
549
+ # Map them so StandardAudit extracts the right records.
550
+ config.actor_extractor = ->(payload) {
551
+ payload[:current_account] || payload[:account]
552
+ }
553
+
554
+ config.target_extractor = ->(payload) {
555
+ # Only set a target when the actor (current_account) differs from the
556
+ # account being acted upon — e.g. an admin locking another user.
557
+ if payload[:current_account]
558
+ target = payload[:account] || payload[:client_application]
559
+ target unless target == payload[:current_account]
560
+ end
561
+ }
534
562
  end
563
+ ```
535
564
 
536
- # config/initializers/standard_id_events.rb
537
- AuditSubscriber.attach
565
+ That's it. Every StandardId authentication event will now be persisted as an audit log entry. No changes are needed inside StandardId itself.
566
+
567
+ #### Querying audit logs
568
+
569
+ ```ruby
570
+ # All auth events for a user
571
+ StandardAudit::AuditLog.for_actor(user).reverse_chronological
572
+
573
+ # Failed logins this week
574
+ StandardAudit::AuditLog
575
+ .by_event_type("standard_id.authentication.attempt.failed")
576
+ .this_week
577
+
578
+ # All activity from an IP address
579
+ StandardAudit::AuditLog.from_ip("192.168.1.1")
538
580
  ```
539
581
 
582
+ See the [StandardAudit README](https://github.com/rarebit-one/standard_audit) for the full query interface, async processing, GDPR compliance, and multi-tenancy support.
583
+
540
584
  ## Account Status (Activation/Deactivation)
541
585
 
542
586
  StandardId provides an optional `AccountStatus` concern for managing account activation and deactivation. This uses Rails enum with the event system to enforce status checks and handle side effects without modifying core authentication logic.
@@ -0,0 +1,18 @@
1
+ module StandardId
2
+ module PasswordlessStrategy
3
+ extend ActiveSupport::Concern
4
+
5
+ STRATEGY_MAP = {
6
+ "email" => StandardId::Passwordless::EmailStrategy,
7
+ "sms" => StandardId::Passwordless::SmsStrategy
8
+ }.freeze
9
+
10
+ private
11
+
12
+ def strategy_for(connection)
13
+ klass = STRATEGY_MAP[connection]
14
+ raise StandardId::InvalidRequestError, "Unsupported connection type: #{connection}" unless klass
15
+ klass.new(request)
16
+ end
17
+ end
18
+ end
@@ -1,10 +1,7 @@
1
1
  module StandardId
2
2
  module Api
3
3
  class PasswordlessController < BaseController
4
- STRATEGY_MAP = {
5
- "email" => StandardId::Passwordless::EmailStrategy,
6
- "sms" => StandardId::Passwordless::SmsStrategy
7
- }.freeze
4
+ include StandardId::PasswordlessStrategy
8
5
 
9
6
  def start
10
7
  raise StandardId::InvalidRequestError, "username, email, or phone_number parameter is required" if start_params[:username].blank?
@@ -16,12 +13,6 @@ module StandardId
16
13
 
17
14
  private
18
15
 
19
- def strategy_for(connection)
20
- klass = STRATEGY_MAP[connection]
21
- raise StandardId::InvalidRequestError, "Unsupported connection type: #{connection}" unless klass
22
- klass.new(request)
23
- end
24
-
25
16
  def start_params
26
17
  return @start_params if @start_params.present?
27
18
 
@@ -3,7 +3,7 @@ module StandardId
3
3
  class LoginController < BaseController
4
4
  include StandardId::InertiaRendering
5
5
  include StandardId::Web::SocialLoginParams
6
-
6
+ include StandardId::PasswordlessStrategy
7
7
 
8
8
  layout "public"
9
9
 
@@ -16,19 +16,62 @@ module StandardId
16
16
  @redirect_uri = params[:redirect_uri] || after_authentication_url
17
17
  @connection = params[:connection]
18
18
 
19
- render_with_inertia props: auth_page_props
19
+ render_with_inertia props: auth_page_props(passwordless_enabled: passwordless_enabled?)
20
20
  end
21
21
 
22
22
  def create
23
+ if passwordless_enabled?
24
+ handle_passwordless_login
25
+ else
26
+ handle_password_login
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def passwordless_enabled?
33
+ StandardId.config.passwordless.enabled
34
+ end
35
+
36
+ def handle_password_login
23
37
  if sign_in_account(login_params)
24
38
  redirect_to params[:redirect_uri] || after_authentication_url, status: :see_other, notice: "Successfully signed in"
25
39
  else
26
40
  flash.now[:alert] = "Invalid email or password"
27
- render_with_inertia action: :show, props: auth_page_props, status: :unprocessable_content
41
+ render_with_inertia action: :show, props: auth_page_props(passwordless_enabled: passwordless_enabled?), status: :unprocessable_content
28
42
  end
29
43
  end
30
44
 
31
- private
45
+ def handle_passwordless_login
46
+ email = login_params[:email].to_s.strip.downcase
47
+ connection = StandardId.config.passwordless.connection
48
+
49
+ if email.blank?
50
+ flash.now[:alert] = "Please enter your email address"
51
+ render_with_inertia action: :show, props: auth_page_props(passwordless_enabled: passwordless_enabled?), status: :unprocessable_content
52
+ return
53
+ end
54
+
55
+ strategy = strategy_for(connection)
56
+
57
+ begin
58
+ strategy.start!(username: email, connection: connection)
59
+ rescue StandardId::InvalidRequestError => e
60
+ flash.now[:alert] = e.message
61
+ render_with_inertia action: :show, props: auth_page_props(passwordless_enabled: passwordless_enabled?), status: :unprocessable_content
62
+ return
63
+ end
64
+
65
+ code_ttl = StandardId.config.passwordless.code_ttl
66
+ signed_payload = Rails.application.message_verifier(:otp).generate(
67
+ { username: email, connection: connection },
68
+ expires_in: code_ttl.seconds
69
+ )
70
+ session[:standard_id_otp_payload] = signed_payload
71
+ session[:return_to_after_authenticating] = params[:redirect_uri] if params[:redirect_uri].present?
72
+
73
+ redirect_to login_verify_path, status: :see_other
74
+ end
32
75
 
33
76
  def redirect_if_authenticated
34
77
  redirect_to after_authentication_url, status: :see_other, notice: "You are already signed in" if authenticated?
@@ -0,0 +1,170 @@
1
+ module StandardId
2
+ module Web
3
+ class LoginVerifyController < BaseController
4
+ include StandardId::InertiaRendering
5
+ include StandardId::PasswordlessStrategy
6
+
7
+ class OtpVerificationFailed < StandardError; end
8
+
9
+ layout "public"
10
+
11
+ skip_before_action :require_browser_session!, only: [:show, :update]
12
+
13
+ before_action :ensure_passwordless_enabled!
14
+ before_action :redirect_if_authenticated, only: [:show]
15
+ before_action :require_otp_payload!
16
+
17
+ def show
18
+ render_with_inertia props: verify_page_props
19
+ end
20
+
21
+ def update
22
+ code = params[:code].to_s.strip
23
+
24
+ if code.blank?
25
+ flash.now[:alert] = "Please enter the verification code"
26
+ render_with_inertia action: :show, props: verify_page_props, status: :unprocessable_content
27
+ return
28
+ end
29
+
30
+ # Record failed attempts outside the main transaction so they persist
31
+ challenge = find_active_challenge
32
+ attempts = record_attempt(challenge, code)
33
+
34
+ if challenge.blank? || !ActiveSupport::SecurityUtils.secure_compare(challenge.code, code)
35
+ emit_otp_validation_failed(attempts) if challenge.present?
36
+
37
+ flash.now[:alert] = "Invalid or expired verification code"
38
+ render_with_inertia action: :show, props: verify_page_props, status: :unprocessable_content
39
+ return
40
+ end
41
+
42
+ strategy = strategy_for(@otp_data[:connection])
43
+
44
+ begin
45
+ ActiveRecord::Base.transaction do
46
+ # Re-fetch with lock inside transaction to prevent concurrent use
47
+ locked_challenge = StandardId::CodeChallenge.lock.find(challenge.id)
48
+ raise OtpVerificationFailed unless locked_challenge.active?
49
+
50
+ account = strategy.find_or_create_account(@otp_data[:username])
51
+ locked_challenge.use!
52
+
53
+ emit_otp_validated(account, locked_challenge)
54
+ session_manager.sign_in_account(account)
55
+ emit_authentication_succeeded(account)
56
+ end
57
+ rescue OtpVerificationFailed
58
+ flash.now[:alert] = "Invalid or expired verification code"
59
+ render_with_inertia action: :show, props: verify_page_props, status: :unprocessable_content
60
+ return
61
+ rescue ActiveRecord::RecordInvalid => e
62
+ flash.now[:alert] = "Unable to complete sign in: #{e.record.errors.full_messages.to_sentence}"
63
+ render_with_inertia action: :show, props: verify_page_props, status: :unprocessable_content
64
+ return
65
+ end
66
+
67
+ session.delete(:standard_id_otp_payload)
68
+
69
+ redirect_to after_authentication_url, status: :see_other, notice: "Successfully signed in"
70
+ end
71
+
72
+ private
73
+
74
+ def ensure_passwordless_enabled!
75
+ return if StandardId.config.passwordless.enabled
76
+
77
+ session.delete(:standard_id_otp_payload)
78
+ redirect_to login_path, alert: "Passwordless login is not available"
79
+ end
80
+
81
+ def redirect_if_authenticated
82
+ redirect_to after_authentication_url, status: :see_other if authenticated?
83
+ end
84
+
85
+ def require_otp_payload!
86
+ signed_payload = session[:standard_id_otp_payload]
87
+
88
+ if signed_payload.blank?
89
+ redirect_to login_path, alert: "Please start the login process"
90
+ return
91
+ end
92
+
93
+ begin
94
+ @otp_data = Rails.application.message_verifier(:otp).verify(signed_payload).symbolize_keys
95
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
96
+ session.delete(:standard_id_otp_payload)
97
+ redirect_to login_path, alert: "Your verification session has expired. Please try again."
98
+ end
99
+ end
100
+
101
+ def find_active_challenge
102
+ StandardId::CodeChallenge.active.find_by(
103
+ realm: "authentication",
104
+ channel: @otp_data[:connection],
105
+ target: @otp_data[:username]
106
+ )
107
+ end
108
+
109
+ def record_attempt(challenge, code)
110
+ return 0 if challenge.blank?
111
+ return 0 if ActiveSupport::SecurityUtils.secure_compare(challenge.code, code)
112
+
113
+ attempts = (challenge.metadata["attempts"] || 0) + 1
114
+ challenge.update!(metadata: challenge.metadata.merge("attempts" => attempts))
115
+
116
+ max_attempts = StandardId.config.passwordless.max_attempts
117
+ challenge.use! if attempts >= max_attempts
118
+
119
+ attempts
120
+ end
121
+
122
+ def emit_otp_validated(account, challenge)
123
+ StandardId::Events.publish(
124
+ StandardId::Events::OTP_VALIDATED,
125
+ account: account,
126
+ channel: @otp_data[:connection]
127
+ )
128
+ StandardId::Events.publish(
129
+ StandardId::Events::PASSWORDLESS_CODE_VERIFIED,
130
+ code_challenge: challenge,
131
+ account: account,
132
+ channel: @otp_data[:connection]
133
+ )
134
+ end
135
+
136
+ def emit_otp_validation_failed(attempts)
137
+ StandardId::Events.publish(
138
+ StandardId::Events::OTP_VALIDATION_FAILED,
139
+ identifier: @otp_data[:username],
140
+ channel: @otp_data[:connection],
141
+ attempts: attempts
142
+ )
143
+ StandardId::Events.publish(
144
+ StandardId::Events::PASSWORDLESS_CODE_FAILED,
145
+ identifier: @otp_data[:username],
146
+ channel: @otp_data[:connection],
147
+ attempts: attempts
148
+ )
149
+ end
150
+
151
+ def emit_authentication_succeeded(account)
152
+ StandardId::Events.publish(
153
+ StandardId::Events::AUTHENTICATION_SUCCEEDED,
154
+ account: account,
155
+ auth_method: "passwordless_otp",
156
+ session_type: "browser"
157
+ )
158
+ end
159
+
160
+ def verify_page_props
161
+ {
162
+ flash: {
163
+ notice: flash[:notice],
164
+ alert: flash[:alert]
165
+ }.compact
166
+ }
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,35 @@
1
+ <%# ERB fallback for non-Inertia rendering. Flash is read directly here;
2
+ Inertia clients receive structured flash via verify_page_props instead. %>
3
+ <% content_for :title, "Verify Code" %>
4
+
5
+ <div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
6
+ <div class="sm:mx-auto sm:w-full sm:max-w-md">
7
+ <h2 class="mt-6 text-center text-2xl/9 font-bold tracking-tight text-gray-900 dark:text-white">Enter verification code</h2>
8
+ <p class="mt-2 text-center text-sm text-gray-500 dark:text-gray-400">We sent you a verification code.</p>
9
+ </div>
10
+
11
+ <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px]">
12
+ <div class="bg-white px-6 py-12 shadow sm:rounded-lg sm:px-12 dark:bg-gray-800/50 dark:shadow-none dark:outline dark:outline-1 dark:-outline-offset-1 dark:outline-white/10">
13
+
14
+ <% if flash[:alert].present? %>
15
+ <div class="mb-4 rounded-md bg-red-50 p-4 text-sm text-red-700 dark:bg-red-900/20 dark:text-red-300"><%= flash[:alert] %></div>
16
+ <% end %>
17
+ <% if flash[:notice].present? %>
18
+ <div class="mb-4 rounded-md bg-green-50 p-4 text-sm text-green-700 dark:bg-green-900/20 dark:text-green-300"><%= flash[:notice] %></div>
19
+ <% end %>
20
+
21
+ <%= form_with url: login_verify_path, method: :patch, local: true, html: { class: "space-y-6" } do |form| %>
22
+ <div>
23
+ <%= form.label :code, "Verification code", class: "block text-sm/6 font-medium text-gray-900 dark:text-white" %>
24
+ <div class="mt-2">
25
+ <%= form.text_field :code, required: true, autofocus: true, autocomplete: "one-time-code", inputmode: "numeric", maxlength: 6, class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500" %>
26
+ </div>
27
+ </div>
28
+
29
+ <div>
30
+ <%= form.submit "Verify", class: "flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:shadow-none dark:hover:bg-indigo-400 dark:focus-visible:outline-indigo-500" %>
31
+ </div>
32
+ <% end %>
33
+ </div>
34
+ </div>
35
+ </div>
@@ -0,0 +1,30 @@
1
+ {
2
+ "ignored_warnings": [
3
+ {
4
+ "fingerprint": "24fc02735a2ad863d6bf1171a4a329b208e9e7c41841fa0149d8e6878d4ce299",
5
+ "note": "Auth engine intentionally redirects to params[:redirect_uri] after signup for OAuth/post-auth flow"
6
+ },
7
+ {
8
+ "fingerprint": "6b35e9906d62a9b9cd0dff9cf53924d40e74bc4f96cfccf27e67e93551113243",
9
+ "note": "Auth engine intentionally redirects to params[:redirect_uri] after logout for OAuth/post-auth flow"
10
+ },
11
+ {
12
+ "fingerprint": "8fddb7968577ac46fdda8e799f476c3eced60cc585da9c30fa61117f91e04ab9",
13
+ "note": "HEAD vs GET distinction is inconsequential here; redirect_to_login adding redirect_uri param on GET-only is safe"
14
+ },
15
+ {
16
+ "fingerprint": "bdbc72619da2ba771b1185ccf16acce801066689bf51adf116eab8c8714b39af",
17
+ "note": "HEAD vs GET distinction is inconsequential here; storing return URL on GET-only is safe"
18
+ },
19
+ {
20
+ "fingerprint": "16bd6ec7c3fa130eb80c15fc90c87f9859d89b37258807bfaffe4101366611a6",
21
+ "note": "Auth engine intentionally redirects to params[:redirect_uri] after login for OAuth/post-auth flow"
22
+ },
23
+ {
24
+ "fingerprint": "e4f96cb212c73c3165c3db6eaa6368c29d362b61264f034e80c9fa6705d72e5b",
25
+ "note": "Auth engine intentionally redirects to params[:redirect_uri] when user is not authenticated"
26
+ }
27
+ ],
28
+ "updated": "2026-02-27",
29
+ "brakeman_version": "8.0.2"
30
+ }
data/config/routes/web.rb CHANGED
@@ -2,6 +2,7 @@ StandardId::WebEngine.routes.draw do
2
2
  scope module: :web do
3
3
  # Authentication flows
4
4
  resource :login, only: [:show, :create], controller: :login
5
+ resource :login_verify, only: [:show, :update], controller: :login_verify
5
6
  resource :logout, only: [:create], controller: :logout
6
7
  resource :signup, only: [:show, :create], controller: :signup
7
8
 
@@ -101,6 +101,8 @@ StandardId.configure do |c|
101
101
  # Events
102
102
  # Enable or disable logging emitted via the internal event system
103
103
  # c.events.enable_logging = false
104
+ #
105
+ # For audit logging, use the standard_audit gem. See the README for wiring instructions.
104
106
 
105
107
  # Social login credentials (if enabled in your app)
106
108
  # c.social.google_client_id = ENV["GOOGLE_CLIENT_ID"]
@@ -12,7 +12,9 @@ module StandardId
12
12
 
13
13
  def current_account
14
14
  return unless current_session
15
- @current_account ||= StandardId.account_class.find_by(id: current_session.account_id)
15
+ @current_account ||= StandardId.account_class
16
+ .find_by(id: current_session.account_id)
17
+ &.tap { |a| a.strict_loading!(false) }
16
18
  end
17
19
 
18
20
  def revoke_current_session!
@@ -23,6 +23,8 @@ StandardConfig.schema.draw do
23
23
  end
24
24
 
25
25
  scope :passwordless do
26
+ field :enabled, type: :boolean, default: false
27
+ field :connection, type: :string, default: "email"
26
28
  field :code_ttl, type: :integer, default: 600 # 10 minutes in seconds
27
29
  field :max_attempts, type: :integer, default: 3
28
30
  field :retry_delay, type: :integer, default: 30 # 30 seconds
@@ -34,7 +34,7 @@ module StandardId
34
34
  channel: connection_type,
35
35
  target: username,
36
36
  code: code,
37
- expires_at: 10.minutes.from_now,
37
+ expires_at: StandardId.config.passwordless.code_ttl.seconds.from_now,
38
38
  ip_address: request.remote_ip,
39
39
  user_agent: request.user_agent
40
40
  )
@@ -1,3 +1,3 @@
1
1
  module StandardId
2
- VERSION = "0.2.9"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -15,7 +15,7 @@ module StandardId
15
15
  end
16
16
 
17
17
  def current_account
18
- Current.account ||= current_session&.account
18
+ Current.account ||= current_session&.account&.tap { |a| a.strict_loading!(false) }
19
19
  end
20
20
 
21
21
  def sign_in_account(account)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.7'
54
+ - !ruby/object:Gem::Dependency
55
+ name: ostruct
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
54
68
  description: StandardId is an authentication engine that provides a complete, secure-by-default
55
69
  solution for identity management, reducing boilerplate and eliminating common security
56
70
  pitfalls.
@@ -68,6 +82,7 @@ files:
68
82
  - app/controllers/concerns/standard_id/api_authentication.rb
69
83
  - app/controllers/concerns/standard_id/inertia_rendering.rb
70
84
  - app/controllers/concerns/standard_id/inertia_support.rb
85
+ - app/controllers/concerns/standard_id/passwordless_strategy.rb
71
86
  - app/controllers/concerns/standard_id/set_current_request_details.rb
72
87
  - app/controllers/concerns/standard_id/social_authentication.rb
73
88
  - app/controllers/concerns/standard_id/web/social_login_params.rb
@@ -85,6 +100,7 @@ files:
85
100
  - app/controllers/standard_id/web/auth/callback/providers_controller.rb
86
101
  - app/controllers/standard_id/web/base_controller.rb
87
102
  - app/controllers/standard_id/web/login_controller.rb
103
+ - app/controllers/standard_id/web/login_verify_controller.rb
88
104
  - app/controllers/standard_id/web/logout_controller.rb
89
105
  - app/controllers/standard_id/web/reset_password/confirm_controller.rb
90
106
  - app/controllers/standard_id/web/reset_password/start_controller.rb
@@ -123,10 +139,12 @@ files:
123
139
  - app/views/standard_id/web/account/show.html.erb
124
140
  - app/views/standard_id/web/auth/callback/providers/mobile_callback.html.erb
125
141
  - app/views/standard_id/web/login/show.html.erb
142
+ - app/views/standard_id/web/login_verify/show.html.erb
126
143
  - app/views/standard_id/web/reset_password/confirm/show.html.erb
127
144
  - app/views/standard_id/web/reset_password/start/show.html.erb
128
145
  - app/views/standard_id/web/sessions/index.html.erb
129
146
  - app/views/standard_id/web/signup/show.html.erb
147
+ - config/brakeman.ignore
130
148
  - config/initializers/generators.rb
131
149
  - config/initializers/migration_helpers.rb
132
150
  - config/routes/api.rb
@@ -214,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
232
  - !ruby/object:Gem::Version
215
233
  version: '0'
216
234
  requirements: []
217
- rubygems_version: 3.6.7
235
+ rubygems_version: 4.0.3
218
236
  specification_version: 4
219
237
  summary: A comprehensive authentication engine for Rails, built on the security primitives
220
238
  introduced in Rails 7/8.