Class: Auth::CustomerSessionsController
- Inherits:
-
Devise::SessionsController
- Object
- Devise::SessionsController
- Auth::CustomerSessionsController
- Defined in:
- app/controllers/auth/customer_sessions_controller.rb
Overview
Controller: customer sessions.
Constant Summary collapse
- AUTH_SCREEN_REFERER =
Auth screens must never be a post-login destination. Full-page
/accounts/registration and /accounts/sign_in submits carry their own URL
as the POST's referer, so the plain safe_referer fallback bounced the
just-signed-in user straight back onto the form they submitted — the
"toast says registered, but I'm still on the registration page" loop.
Optional locale prefix (/en-US, /en-CA) per the routing scope. %r{\A(?:/[a-z]{2}-[A-Z]{2})?/(?:accounts|customer_sessions)(?:/|\z)}
Constants included from Controllers::DeviseReturnable
Controllers::DeviseReturnable::ALLOWED_REDIRECT_HOSTS
Instance Method Summary collapse
-
#authenticate ⇒ void
POST /accounts/authenticate — verifies the submitted password and signs the account in on success.
-
#destroy ⇒ void
DELETE /accounts/sign_out — signs the account out and fully resets the session so no www-only state survives into a later browsing session.
-
#fast_checkout ⇒ void
GET /accounts/fast_checkout — #start's checkout-flow variant, returning to the cart on either branch instead of
/my_account. -
#finish_fast_checkout ⇒ void
POST /accounts/finish_fast_checkout — #set_email's checkout-flow variant: resolves
login/passwordand signs in directly rather than stepping through the choose-account/register turbo frames. -
#new ⇒ void
GET /accounts/sign_in — sign-in form; bounces an already-signed-in account away with a notice.
-
#omniauth_register ⇒ void
GET /accounts/omniauth_register — starts an OAuth sign-up by POSTing to the provider's OmniAuth authorize path for a supported
provider. -
#register ⇒ void
POST /accounts/register — creates the customer's account, guarded against clobbering an already-promoted context user.
-
#registration ⇒ void
GET /accounts/registration — the registration form, pre-filled from OAuth data or a submitted
login, guarded against clobbering an already-promoted context user. -
#set_email ⇒ void
PATCH /accounts/set_email — resolves the submitted
login/email to the right next step (sign in, register, choose account, or reset password) and re-renders the login-content turbo frame accordingly. -
#start ⇒ void
GET /accounts/start — the "continue with email" first step of the combined sign-in/registration flow.
Methods included from MyAccountHelper
#account_nav_active?, #can_list_contact_resources?, #is_current_user_or_customer_login_email?, #setup_contact, #setup_customer_account, #sorted_contact_points
Methods included from Controllers::DeviseReturnable
#check_for_devise_return_path, #devise_return_path_from_omniauth_hash, #safe_referer
Instance Method Details
#authenticate ⇒ void
This method returns an undefined value.
POST /accounts/authenticate — verifies the submitted password and signs
the account in on success.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 250 def authenticate respond_to do |format| @account = Account.where(login: params[:account][:login].to_s.downcase).first if @account authenticated = @account.valid_password?(params[:account][:password]) if authenticated && !@account.disabled? request.env[AUTHTRAIL_TRACKED_STRATEGY_ENV_KEY] = 'database_authenticatable' sign_in(:account, @account) # Bypass Devise strategy means after_database_authentication # doesn't fire; drain any pending bcrypt-migration rehash here. @account.consume_password_rehash! logger.debug 'CustomerSessionsController authenticate' flash[:info] = 'Awesome, you have successfully signed in.' redirect_path = post_login_redirect_path format.turbo_stream { render turbo_stream: turbo_stream.redirect(redirect_path) } format.html { redirect_to(redirect_path) } elsif @account.disabled? flash.now[:error] = @account. format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/authenticate') } format.html { render :new, status: :unprocessable_content } else flash.now[:error] = 'Sorry, incorrect password. Please try again.' format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/authenticate') } format.html { render :new, status: :unprocessable_content } end else flash.now[:error] = 'Invalid login. Please try again' format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/login_form') } format.html { render :new, status: :unprocessable_content } end end end |
#destroy ⇒ void
This method returns an undefined value.
DELETE /accounts/sign_out — signs the account out and fully resets the
session so no www-only state survives into a later browsing session.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 36 def destroy clear_post_logout_session super # Belt-and-suspenders: even though `clear_post_logout_session` already # drops every www-only key we know about, a full session reset on # logout is the cleaner contract — anything else stashed in the # session (visit_id, devise.* flags, ad-hoc breadcrumbs from any # controller) gets wiped too. Without this, a promoted guest party # from the post-logout browsing session can carry forward into a # later session if the same browser comes back days later (the # session cookie outlives the logout). Preserve Devise's # "signed out" flash since reset_session would otherwise wipe it. notice = flash[:notice] reset_session flash[:notice] = notice if notice.present? end |
#fast_checkout ⇒ void
This method returns an undefined value.
GET /accounts/fast_checkout — #start's checkout-flow variant, returning
to the cart on either branch instead of /my_account.
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 83 def fast_checkout logger.debug "CustomerSessionsController#start! delocalized_path(request.referer): #{delocalized_path(request.referer)}" if request.referer.present? set_up_alerts_by_parameters @account = current_account if @account&.valid? flash[:info] = 'You are already signed in.' respond_to do |format| format.turbo_stream { render turbo_stream: turbo_stream.redirect(@devise_return_path || my_account_path) } format.html do flash[:error] = 'You are already signed in.' redirect_to(@devise_return_path || checkout_my_cart_path) end end else respond_to do |format| format.turbo_stream {} format.html { redirect_to(@devise_return_path || checkout_my_cart_path) } end end end |
#finish_fast_checkout ⇒ void
This method returns an undefined value.
POST /accounts/finish_fast_checkout — #set_email's checkout-flow
variant: resolves login/password and signs in directly rather than
stepping through the choose-account/register turbo frames.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 180 def finish_fast_checkout set_up_alerts_by_parameters login = params[:login].presence password = params[:password].presence respond_to do |format| if login && password @account_by_login = Account.where(login: login).first_or_initialize @account_by_email = Account.where(email: login) ## First we check wether the login doesn't exist but there is an account with that email if @account_by_login.new_record? && @account_by_email.first.nil? @account = @account_by_login elsif @account_by_login.new_record? && @account_by_email.first.present? @account = @account_by_email.first if @account_by_email.size == 1 @account = nil if @account_by_email.size > 1 else @account = @account_by_login end # When multiple accounts share the same email, deterministically # pick the most recently used one and try the password against # ONLY that account. Iterating across every account-with-this-email # let one POST burn N bcrypt comparisons, accelerating credential # stuffing. Users with stale duplicate accounts can recover via # "Forgot Your Password?". @account = @account_by_email.order(last_sign_in_at: :desc).first if @account.nil? if @account.nil? || @account.new_record? flash[:error] = "We couldn't find any account with that login." format.turbo_stream { render action: :fast_checkout } else if @account.auth_token_required? flash[:info] = "#{@account.login} is already linked to a guest account with no password or social login. You will need to use your 'WarmlyYours Shopping Cart Misses You' e-mail link to access your account (and add a password or social login), or use the #{view_context.link_to 'Forgot Your Password?', new_account_password_path} link to create your password.".html_safe end authenticated = @account.valid_password?(password) if authenticated && !@account.disabled? request.env[AUTHTRAIL_TRACKED_STRATEGY_ENV_KEY] = 'database_authenticatable' sign_in(:account, @account) # If this fails, check if the account is confirmed by calling @account.confirmed? # Bypass Devise strategy means after_database_authentication # doesn't fire; drain any pending bcrypt-migration rehash here. @account.consume_password_rehash! # This is now removed, but kept in for reference, 2/28/18, - Ramie # logger.debug "CustomerSessionsController finish_fast_checkout, JWT Token is #{env['warden-jwt_auth.token']}" flash[:info] = 'Awesome, you have successfully signed in.' if @devise_return_path.present? && @devise_return_path.include?('smart_services') format.turbo_stream { render action: :smart_services_checkout } else format.turbo_stream { render turbo_stream: turbo_stream.redirect(@devise_return_path || checkout_my_cart_path) } end elsif @account.disabled? flash[:error] = @account. format.turbo_stream { render action: :fast_checkout } else flash[:error] = "Sorry, incorrect password. Please try again. #{view_context.link_to 'Forgot your password?', new_account_password_path}".html_safe format.turbo_stream { render action: :fast_checkout } end end else flash[:error] = "Please enter a valid email address and password. #{view_context.link_to 'Forgot your password?', new_account_password_path}".html_safe format.turbo_stream { render action: :fast_checkout } end end end |
#new ⇒ void
This method returns an undefined value.
GET /accounts/sign_in — sign-in form; bounces an already-signed-in
account away with a notice.
23 24 25 26 27 28 29 30 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 23 def new logger.debug "CustomerSessionsController#new! @devise_return_path: #{@devise_return_path}, params[:login]: #{params[:login]}" @account = current_account return unless @account&.valid? flash[:error] = 'You are already signed in.' redirect_to(@devise_return_path || my_account_path) end |
#omniauth_register ⇒ void
This method returns an undefined value.
GET /accounts/omniauth_register — starts an OAuth sign-up by POSTing to
the provider's OmniAuth authorize path for a supported provider.
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 287 def omniauth_register @account = current_account if @account&.valid? flash[:error] = 'You are already signed in.' redirect_to(@devise_return_path || my_account_path) else @provider = nil provider = params[:provider] if Authentication::SUPPORTED_PROVIDERS.include?(params[:provider]) @provider = provider.to_sym if provider if @provider redirect_post delocalized_path((:account, provider, authenticity_token: form_authenticity_token, devise_return_path: @devise_return_path)) else flash[:error] = 'Unh-unh, that is an unsupported provider.' redirect_to new_account_session_path(devise_return_path: @devise_return_path) end end end |
#register ⇒ void
This method returns an undefined value.
POST /accounts/register — creates the customer's account, guarded
against clobbering an already-promoted context user.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 352 def register # The clobber guard must run outside `respond_to`: a `return` from inside a # `respond_to` block exits the action before the responder is invoked, so # Rails falls back to implicitly rendering `register.turbo_stream.erb` — # which renders `_register` with `@account` still nil (it is assigned # below, after this guard). See `#registration` for the same pattern. if registration_would_clobber_existing_identity? log_clobber_attempt('register') respond_to do |format| format.turbo_stream do # Turbo Stream re-renders the login form in place, so flash.now is # the correct flavor (no second request to read flash from the # session). flash.now[:error] = render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/login_form') end format.html do # Plain HTML redirects need the message to survive into the next # request, so we use flash (not flash.now) which is read on the # follow-up GET to the sign-in page. flash[:error] = redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: params.dig(:account, :login).presence) end end return end respond_to do |format| @account = @context_user.build_account(params[:account]) @account.login ||= @account.email # dig, not [][] — a POST to /register with no `account` param at all (bot # probes, a JS submit bug) used to 500 here on `nil[]` (AppSignal #6418). # Leaving both nil lets `require_password` fail validation normally, so # the caller gets the ordinary "Password can't be blank" form re-render. @password = params.dig(:account, :password) @password_confirmation = params.dig(:account, :password_confirmation) @account.require_password = true apply_registration_customer_attributes(params[:customer]) @context_user.set_email_from_account @context_user.leadify @omniauth = get_omniauth_session_from_session_or_params if @omniauth @account.apply_omniauth(@omniauth) @account.require_password = false end begin if @context_user.save track_event('Lead - Form Submitted', { leadType: 'Registration', source: 'customer_registration' }) track_event('Account - Registration Completed', { accountId: @context_user.id, leadType: registration_lead_type(@omniauth) }) logger.debug 'CustomerSessionsController register' flash[:info] = 'Cool! You have successfully registered your WarmlyYours account.' session.delete(:guest_user_id) request.env[AUTHTRAIL_TRACKED_STRATEGY_ENV_KEY] = 'registration' sign_in(:account, @account) redirect_path = post_login_redirect_path format.turbo_stream { render turbo_stream: turbo_stream.redirect(redirect_path) } format.html { redirect_to redirect_path } else @report_errors_for = [@context_user] flash.now[:error] = @context_user.errors..to_sentence format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/register') } format.html { render :registration } end rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation => e # Gracefully handle race conditions hitting unique DB constraints (e.g. index_accounts_on_login) ErrorReporting.warning('Registration uniqueness violation', error: e., login: @account.login) @account.errors.add(:login, 'has already been taken') if e..to_s.include?('index_accounts_on_login') @report_errors_for = [@account, @context_user] flash.now[:error] = 'That username is already taken. Please choose another.' format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/register') } format.html { render :registration } end end end |
#registration ⇒ void
This method returns an undefined value.
GET /accounts/registration — the registration form, pre-filled from
OAuth data or a submitted login, guarded against clobbering an
already-promoted context user.
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 310 def registration # A signed-in account has nothing to register — render the form and the # subsequent POST would only trip the clobber guard. This mirrors #new. # (OAuth users completing a registration are NOT signed in yet, so the # missing-info flow from Auth::AuthenticationsController still renders.) if current_account&.persisted? flash[:info] = 'You are already signed in.' redirect_to(@devise_return_path.presence || my_account_path) and return end # Same defense-in-depth as #register POST: never render a registration # form scoped to a party that already has somebody else's account on it. if @context_user&.account&.persisted? && current_account.nil? log_clobber_attempt('registration') flash[:error] = redirect_to(new_account_session_path(devise_return_path: @devise_return_path)) and return end @account = current_account || @context_user.build_account @account.require_password = true @omniauth = get_omniauth_session_from_session_or_params if @omniauth @account.apply_omniauth(@omniauth) @account.require_password = false @context_user.email = @account.email @context_user.name = Authentication.extract_name_from_omniauth_hash(@omniauth) elsif params[:login].present? # Direct registration entry from "Continue with Email" after the email # check determined no account exists. Pre-fill login/email so the user # doesn't have to type it again. login = params[:login].to_s.strip @account.login = login @account.email = login if login.include?('@') @account.set_default_marketing_preferences(I18n.locale) end end |
#set_email ⇒ void
This method returns an undefined value.
PATCH /accounts/set_email — resolves the submitted login/email to the
right next step (sign in, register, choose account, or reset password)
and re-renders the login-content turbo frame accordingly.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 109 def set_email set_up_alerts_by_parameters login = params[:login].presence respond_to do |format| if login @account_by_login = Account.where(login: login).first_or_initialize @account_by_email = Account.where(email: login) ## First we check whether the login doesn't exist but there is an account with that email if @account_by_login.new_record? && @account_by_email.first.nil? @account = @account_by_login elsif @account_by_login.new_record? && @account_by_email.first.present? @account = @account_by_email.first if @account_by_email.size == 1 @account = nil if @account_by_email.size > 1 else @account = @account_by_login end # Now we check what to do with the account if @account.nil? flash.now[:error] = "We found multiple accounts with that email. Please use 'Forgot your password?' to proceed." format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/choose_account') } format.html do flash[:error] = "We found multiple accounts with that email. Please use 'Forgot your password?' to proceed." redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: login) end elsif @account.new_record? @account.push_login_to_email @account.require_password = true @account.set_default_marketing_preferences(I18n.locale) flash.now[:info] = 'Create your account to continue.' format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/register') } format.html do flash[:info] = 'Create your account to continue.' redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: login) end elsif @account.disabled? flash.now[:error] = @account. format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/login_form') } format.html do flash[:error] = @account. redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: login) end else if @account.auth_token_required? flash.now[:info] = "#{@account.login} is already linked to a guest account with no password or social login. You will need to use your 'WarmlyYours Shopping Cart Misses You' e-mail link to access your account (and add a password or social login), or use the #{view_context.link_to 'Forgot Your Password?', new_account_password_path} link to create your password.".html_safe end format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/authenticate') } format.html do flash[:info] ||= 'Please enter your password to continue.' redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: login) end end else flash.now[:error] = 'Please enter a valid email address.' format.turbo_stream { render turbo_stream: turbo_stream.update(turbo_stream_target, partial: 'auth/customer_sessions/login_form') } format.html do flash[:error] = 'Please enter a valid email address.' redirect_to new_account_session_path(devise_return_path: @devise_return_path) end end end end |
#start ⇒ void
This method returns an undefined value.
GET /accounts/start — the "continue with email" first step of the
combined sign-in/registration flow.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'app/controllers/auth/customer_sessions_controller.rb', line 58 def start logger.debug "CustomerSessionsController#start! @devise_return_path: #{@devise_return_path}" logger.debug "CustomerSessionsController#start! params[:login]: #{params[:login]}" logger.debug "CustomerSessionsController#start! delocalized_path(request.referer): #{delocalized_path(request.referer)}" if request.referer.present? set_up_alerts_by_parameters respond_to do |format| @account = current_account if @account&.valid? flash[:info] = 'You are already signed in.' format.turbo_stream { render turbo_stream: turbo_stream.redirect(@devise_return_path || my_account_path) } format.html do flash[:error] = 'You are already signed in.' redirect_to(@devise_return_path || my_account_path) end else format.turbo_stream {} format.html { redirect_to new_account_session_path(devise_return_path: @devise_return_path, login: params[:login]) } end end end |