rails_simple_auth 1.1.0 → 1.2.1

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: ad68672c8d3cb2ae020bb5ca15c50806b64a3593734088c5bae4ed351b1683d1
4
- data.tar.gz: 195845cbc41e1163f5212f3dc2448093bb13a278dbf58332189e115983ed90b8
3
+ metadata.gz: 92f4718f32ac9f010bc24be665915952c9dcf5d02340c07e7692b2a053a282ce
4
+ data.tar.gz: 71ba9817ebe4fcefd815aa47e0f99d02261349d78a0532b0a6f3fca22442d6ab
5
5
  SHA512:
6
- metadata.gz: cc42b623955ccdfcc4737eb58c2b70efb0fb93f6c7633791d81d89ed8f0ce396277dddae529a25e38906324ef7256e34bcbcfb0bbe3dc3314a209c0c9e922dab
7
- data.tar.gz: b496b38e1c2369759e8dbe21b927f21d43a5cd79da47882290e322444658053b7a415cfe42888a35f3cc5ac1101c4b1a0a7bf91f68fa8859f7d883e0c1594d44
6
+ metadata.gz: 97e9c562f6993c03661ef81eee4e534ad25496b3f36f3f46d1b559a860835a8dde49f190eeed0368a5abc7d0d06e11d2a596873d75af7484983f3afe043397d5
7
+ data.tar.gz: cf069335a5b94f84b163057116730d1feb4d428d43b9c3ccb5da056f73b92a13ef2dbeda7cf8c939c858480243bcf93e47ec7899f63c12864398d21d4fcd43c3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.1] - 2026-09-15
11
+
12
+ ### Fixed
13
+
14
+ - OAuth linking reloads the account under a row lock so concurrent confirmation or pending email changes are preserved; if the matched email changes before linking, sign-in is rejected.
15
+ - Confirm existing accounts when linking by email through a trusted OAuth provider, before calling the host application's `assign_oauth_attributes` hook. Preserve existing confirmation timestamps and pending email changes. Accounts found by provider and UID remain unchanged.
16
+ - Return `nil` when saving an existing OAuth account link fails, preventing authentication with unsaved changes.
17
+
18
+ ## [1.2.0] - 2026-05-23
19
+
20
+ ### Changed
21
+
22
+ - **OAuth provider sign-in now honors `session[:return_to]`.** `OmniauthCallbacksController#create` previously hardcoded `after_sign_in_path`, so a user mid-redirect (e.g., a host app that called `store_location_for_redirect` before bouncing to `/sign_in`) landed on the default page after the Google/GitHub round-trip instead of resuming. It now goes through `sign_in_user_and_redirect`, which consumes `session[:return_to]` and falls back to `after_sign_in_path`. **Action:** host apps relying on a hard reset to the configured path post-OAuth should review their flows; the configured path remains the default when nothing was stored.
23
+ - **Confirmation-disabled sign-up now honors `session[:return_to]`.** `RegistrationsController#after_successful_registration`'s no-confirmation branch previously hardcoded `after_sign_up_path`. It now redirects to `stored_location_or_default(:after_sign_up_path)` — same precedence rule, separate config key. The `after_sign_up_callback` execution path is unchanged.
24
+ - **`stored_location_or_default` now accepts an optional fallback config key** (default `:after_sign_in_path`), so callers can pass `:after_sign_up_path` (or any other configured path) without duplicating the lookup logic.
25
+ - **`stored_location_or_default` validates the stored value at read time** as defense-in-depth: a poisoned `session[:return_to]` (non-string, doesn't start with `/`, or starts with `//`) is rejected and the fallback is used instead. The write-side validation in `store_location_for_redirect` remains the primary defense; this read-side check protects host apps that bypass the helper.
26
+ - **Extracted `sign_in_user_and_redirect(user, notice:)` to `SessionManagement` concern.** `SessionsController#sign_in_and_redirect` and `OmniauthCallbacksController#create` now share one definition of the sign-in-and-redirect sequence (`destroy_temporary_user_session` → `create_session_for` → `run_after_sign_in_callback` → `redirect_to stored_location_or_default`). `SessionsController#sign_in_and_redirect`'s external behavior is preserved.
27
+
10
28
  ## [1.1.0] - 2026-01-20
11
29
 
12
30
  ### Added
data/README.md CHANGED
@@ -125,12 +125,12 @@ Or edit `rails_simple_auth.css` directly for complete control.
125
125
 
126
126
  | Variable | Default | Description |
127
127
  |----------|---------|-------------|
128
- | `--rsa-color-primary` | `#3b82f6` | Primary button/link color |
129
- | `--rsa-color-primary-hover` | `#2563eb` | Primary hover state |
128
+ | `--rsa-color-primary` | `#4f46e5` | Primary button/link color |
129
+ | `--rsa-color-primary-hover` | `#4338ca` | Primary hover state |
130
130
  | `--rsa-color-background-form` | `#ffffff` | Form container background |
131
- | `--rsa-color-text` | `#374151` | Main text color |
132
- | `--rsa-color-text-muted` | `#6b7280` | Secondary text color |
133
- | `--rsa-color-border` | `#e5e7eb` | Border color |
131
+ | `--rsa-color-text` | `#475569` | Main text color |
132
+ | `--rsa-color-text-muted` | `#64748b` | Secondary text color |
133
+ | `--rsa-color-border` | `#e2e8f0` | Border color |
134
134
  | `--rsa-color-danger` | `#dc2626` | Error message color |
135
135
 
136
136
  ## View Customization
@@ -217,6 +217,12 @@ class User < ApplicationRecord
217
217
  end
218
218
  ```
219
219
 
220
+ Email-based account linking is enabled by default (`config.oauth_link_existing_accounts = true`). Only enable it for providers you trust to supply verified email addresses; the gem does not inspect provider-specific verification claims. Set it to `false` to reject linking by email.
221
+
222
+ New OAuth accounts and existing accounts linked by email are automatically confirmed when they have a `confirmed_at` column. Existing confirmation timestamps and pending email changes are preserved. Confirmation is assigned before `assign_oauth_attributes`, so your hook can use it to update application-specific state. The hook and confirmation changes are saved together; a failed save returns `nil`.
223
+
224
+ Accounts found by provider and UID return unchanged, including their email and confirmation state. Applications with historical unconfirmed OAuth accounts should handle their backfill separately.
225
+
220
226
  ## Temporary Users (Guest Accounts)
221
227
 
222
228
  Temporary users allow visitors to try your app without creating an account. They get a real user record with full functionality, then can convert to a permanent account later by providing email and password.
@@ -19,11 +19,7 @@ module RailsSimpleAuth
19
19
  display_name = RailsSimpleAuth.configuration.oauth_provider_display_name(provider)
20
20
 
21
21
  if user&.persisted?
22
- destroy_temporary_user_session(user)
23
- create_session_for(user)
24
- run_after_sign_in_callback(user)
25
- redirect_to resolve_path(:after_sign_in_path),
26
- notice: "Signed in successfully with #{display_name}."
22
+ sign_in_user_and_redirect(user, notice: "Signed in successfully with #{display_name}.")
27
23
  else
28
24
  redirect_to new_session_path, alert: "Could not authenticate with #{display_name}."
29
25
  end
@@ -40,7 +40,7 @@ module RailsSimpleAuth
40
40
  else
41
41
  create_session_for(@user)
42
42
  run_after_sign_up_callback(@user)
43
- redirect_to resolve_path(:after_sign_up_path), notice: 'Account created successfully!'
43
+ redirect_to stored_location_or_default(:after_sign_up_path), notice: 'Account created successfully!'
44
44
  end
45
45
  end
46
46
 
@@ -96,10 +96,7 @@ module RailsSimpleAuth
96
96
  end
97
97
 
98
98
  def sign_in_and_redirect(user)
99
- destroy_temporary_user_session(user)
100
- create_session_for(user)
101
- run_after_sign_in_callback(user)
102
- redirect_to stored_location_or_default, notice: 'Signed in successfully.'
99
+ sign_in_user_and_redirect(user)
103
100
  end
104
101
  end
105
102
  end
@@ -88,8 +88,26 @@ module RailsSimpleAuth
88
88
  end
89
89
  end
90
90
 
91
- def stored_location_or_default
92
- session.delete(:return_to) || resolve_path(:after_sign_in_path)
91
+ # Returns the stored post-auth return path (consuming it from the session) or,
92
+ # if none was stored or the stored value fails defense-in-depth validation,
93
+ # the resolved fallback path for the given config key (e.g., :after_sign_in_path,
94
+ # :after_sign_up_path).
95
+ #
96
+ # SECURITY: store_location_for_redirect validates paths at write time, but
97
+ # nothing prevents host-app code from writing to session[:return_to] directly.
98
+ # We re-check here so a poisoned value (open-redirect, javascript:, malformed
99
+ # string) falls back instead of raising UnsafeRedirectError or worse.
100
+ #
101
+ # NOTE: mutates the session — calling twice returns the fallback the second time.
102
+ def stored_location_or_default(fallback_path_config = :after_sign_in_path)
103
+ stored = session.delete(:return_to)
104
+ return resolve_path(fallback_path_config) unless safe_stored_location?(stored)
105
+
106
+ stored
107
+ end
108
+
109
+ def safe_stored_location?(path)
110
+ path.is_a?(String) && path.start_with?('/') && !path.start_with?('//')
93
111
  end
94
112
 
95
113
  def redirect_to_sign_in
@@ -8,6 +8,19 @@ module RailsSimpleAuth
8
8
 
9
9
  private
10
10
 
11
+ # Standard sign-in sequence: clean up the temporary user, create the real
12
+ # session, fire after_sign_in callbacks, then redirect.
13
+ #
14
+ # NOTE: order matters — destroy_temporary_user_session must run before
15
+ # create_session_for, otherwise the new session cookie is wiped when the
16
+ # temp user is destroyed.
17
+ def sign_in_user_and_redirect(user, notice: 'Signed in successfully.')
18
+ destroy_temporary_user_session(user)
19
+ create_session_for(user)
20
+ run_after_sign_in_callback(user)
21
+ redirect_to stored_location_or_default, notice: notice
22
+ end
23
+
11
24
  # Create a new session for the user and set the cookie
12
25
  def create_session_for(user)
13
26
  session_record = user.sessions.create!(
@@ -41,16 +41,38 @@ module RailsSimpleAuth
41
41
  existing_user = find_by_email(email)
42
42
  if existing_user
43
43
  # Configurable: allow linking OAuth to existing accounts
44
- # Default is true - most OAuth providers (Google, GitHub) verify emails
44
+ # Only enable for providers trusted to supply verified email addresses.
45
45
  if RailsSimpleAuth.configuration.oauth_link_existing_accounts
46
- Rails.logger.info(
47
- "[RailsSimpleAuth] OAuth linked to existing account: #{email}. " \
48
- "Provider: #{provider}."
49
- )
50
- # Call hook to store OAuth credentials on existing user
51
- existing_user.assign_oauth_attributes(auth_hash) if existing_user.respond_to?(:assign_oauth_attributes)
52
- existing_user.save if existing_user.changed?
53
- return existing_user
46
+ matched_email = existing_user.email
47
+ return existing_user.with_lock do
48
+ # Reload under the lock before checking confirmation state.
49
+ next nil if existing_user.email != matched_email
50
+
51
+ # Confirm the current email only; OAuth does not verify a pending replacement.
52
+ pending_email = existing_user.respond_to?(:unconfirmed_email) &&
53
+ existing_user.unconfirmed_email.present?
54
+ if existing_user.respond_to?(:confirmed_at=) && existing_user.confirmed_at.nil? && !pending_email
55
+ existing_user.confirmed_at = Time.current
56
+ end
57
+
58
+ # Call hook to store OAuth credentials on existing user
59
+ if existing_user.respond_to?(:assign_oauth_attributes)
60
+ existing_user.assign_oauth_attributes(auth_hash)
61
+ end
62
+ if existing_user.changed? && !existing_user.save
63
+ Rails.logger.error(
64
+ "[RailsSimpleAuth] OAuth account linking failed for email: #{email}, " \
65
+ "provider: #{provider}, errors: #{existing_user.errors.full_messages.join(', ')}"
66
+ )
67
+ next nil
68
+ end
69
+
70
+ Rails.logger.info(
71
+ "[RailsSimpleAuth] OAuth linked to existing account: #{email}. " \
72
+ "Provider: #{provider}."
73
+ )
74
+ existing_user
75
+ end
54
76
  else
55
77
  Rails.logger.warn(
56
78
  "[RailsSimpleAuth] OAuth login rejected for existing email: #{email}. " \
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSimpleAuth
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_simple_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Kuznetsov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-01-20 00:00:00.000000000 Z
11
+ date: 2026-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bcrypt