Module: CamaleonCms::SessionHelper

Included in:
SessionRuntimeConcern
Defined in:
app/helpers/camaleon_cms/session_helper.rb

Instance Method Summary collapse

Instance Method Details

#cama_authenticate(redirect_uri = nil) ⇒ Object

check if a visitor was logged in if the user was not logged in, then redirect to login url



216
217
218
219
220
221
222
223
224
225
226
227
# File 'app/helpers/camaleon_cms/session_helper.rb', line 216

def cama_authenticate(redirect_uri = nil)
  params[:return_to] = redirect_uri
  return if cama_sign_in?

  flash[:error] = t('camaleon_cms.admin.login.please_login')
  cookies[:return_to] = if params[:return_to].present?
                          params[:return_to]
                        else
                          (request.get? && params[:controller] != 'admin/sessions' ? request.original_url : nil)
                        end
  redirect_to 
end

#cama_current_roleObject

return the role for the current user if not logged in, then return 'public'



184
185
186
# File 'app/helpers/camaleon_cms/session_helper.rb', line 184

def cama_current_role
  current_site.visitor_role
end

#cama_current_userObject

return the current user logged in



189
190
191
192
193
194
195
196
197
198
199
200
# File 'app/helpers/camaleon_cms/session_helper.rb', line 189

def cama_current_user
  # Honor an externally-set user, and memoize a nil resolution via user_resolved so a signed-out
  # request (or a present-but-stale auth cookie) is not re-resolved on every call (regression M16).
  return CurrentRequest.user if CurrentRequest.user || CurrentRequest.user_resolved

  CurrentRequest.user_resolved = true
  user = cama_calc_api_current_user # api current user...
  if user.nil? && cookie_auth_token_complete?
    user = current_site.users_include_admins.find_by(auth_token: user_auth_token_from_cookie).try(:decorate)
  end
  CurrentRequest.user = user
end

#cama_get_session_idObject

return the session id



230
231
232
233
234
235
# File 'app/helpers/camaleon_cms/session_helper.rb', line 230

def cama_get_session_id
  session[:autor] = 'Owen Peredo Diaz' if request.session_options[:id].blank?
  id = request.session_options[:id]
  id = id.public_id if id.instance_of?(::Rack::Session::SessionId)
  id
end

#cama_impersonation_parent_userObject

The admin who started the current impersonation, resolved from the stashed parent auth token (same lookup as cama_current_user). Returns nil if there is no active impersonation or the stash no longer resolves to a user (e.g. the admin rotated their token by changing their password). Callers MUST re-authenticate this user before session_back_to_parent (H6 residual): the impersonated session an admin holds is indistinguishable from one they abandon, so the admin's password is the only proof that the person returning is the admin and not a later occupant.



134
135
136
137
138
139
# File 'app/helpers/camaleon_cms/session_helper.rb', line 134

def cama_impersonation_parent_user
  token = session[:parent_auth_token].to_s.split('&').first
  return if token.blank?

  current_site.users_include_admins.find_by(auth_token: token)
end

#cama_logout_userObject

logout current user



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'app/helpers/camaleon_cms/session_helper.rb', line 154

def cama_logout_user
  # Security (audit 2026-08-11 M3): rotate the server-side token so a cookie copied before logout
  # cannot be replayed afterwards. The token is per-user, so this also ends the user's other sessions.
  # Audit M14: skip the rotation during impersonation -- cama_current_user is then the impersonated
  # user, not the admin ending the session, and rotating their token would log the innocent user
  # out of all their own devices. session_back_to_parent is the intended exit from impersonation.
  cama_current_user&.cama_reset_auth_token! if session[:parent_auth_token].blank?
  cookies.delete(:auth_token, domain: :all)
  cookies.delete(:auth_token, domain: nil)
  c_data = { value: nil, expires: 24.hours.ago }
  c_data[:domain] = :all if PluginRoutes.system_info['users_share_sites'].present? && CamaleonCms::Site.count > 1
  cookies[:auth_token] = c_data
  CurrentRequest.user = nil
  CurrentRequest.user_resolved = true # the cookie is gone; don't re-resolve to a stale user
  # Drop all server-side session state on logout (H6): a lingering impersonation parent_auth_token
  # must not survive to be restored into a later session on a shared browser.
  reset_session
  cama_safe_redirect(params[:return_to], ,
                     notice: t('camaleon_cms.admin.logout.message.closed'))
end

#cama_on_heroku?Boolean

check if current host is heroku

Returns:

  • (Boolean)


110
111
112
# File 'app/helpers/camaleon_cms/session_helper.rb', line 110

def cama_on_heroku?
  ENV.keys.any? { |var_name| var_name.match(/(heroku|dyno)/i) }
end

#cama_register_user(user_data, meta) ⇒ Object

User registration.

user_data must contain:

  • first_name
  • email
  • username
  • password
  • password_confirmation


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/helpers/camaleon_cms/session_helper.rb', line 70

def cama_register_user(user_data, meta)
  user = current_site.users.new(user_data)

  # Gate on the register captcha before running any hook, so user_before_register does not fire for
  # attempts that fail the captcha. This diverges from user_before_login, which runs its hook even on a
  # failed captcha and passes the result as r[:captcha_validate]; user_before_register does neither, so
  # the "parity with user_before_login" below is limited to the stop_process veto. Verifying here also
  # consumes the single-use captcha before the hook, so a later veto burns a solved challenge —
  # harmless, since the re-rendered form issues a fresh one.
  if current_site.security_user_register_captcha_enabled? && !cama_captcha_verified?
    return { result: false, type: :captcha_error,
             message: t('camaleon_cms.admin.users.message.error_captcha'), user: user }
  end

  r = { user: user, params: params, stop_process: false }
  # Broadcast (hooks_run), not hook_run: hook_run(target, name, …) takes the app as its first arg, so
  # hook_run('user_before_register', r) treated the name as a plugin and silently no-op'd — the hook
  # never fired. Match the sibling user_before_login/user_after_register broadcasts.
  hooks_run('user_before_register', r)
  # A handler may veto the registration by setting r[:stop_process] (parity with user_before_login):
  # stop here without saving. The handler may surface its own reason (add to r[:user].errors, or
  # render/redirect); otherwise the controller shows a generic error.
  return { result: false, type: :stopped, user: user } if r[:stop_process]

  if user.save
    user.set_metas(meta)
    message = if current_site.need_validate_email?
                t('camaleon_cms.admin.users.message.created_pending_validate_email')
              else
                t('camaleon_cms.admin.users.message.created')
              end
    r = { user: user, message: message, redirect_url:  }
    hooks_run('user_after_register', r)
    { result: true, message: r[:message], redirect_url: r[:redirect_url], user: user }
  else
    { result: false, type: :no_saved, user: user }
  end
end

#cama_sign_in?Boolean Also known as: signin?

Check if the current user is already signed

Returns:

  • (Boolean)


176
177
178
# File 'app/helpers/camaleon_cms/session_helper.rb', line 176

def cama_sign_in?
  !cama_current_user.nil?
end

Returns:

  • (Boolean)


202
203
204
# File 'app/helpers/camaleon_cms/session_helper.rb', line 202

def cookie_auth_token_complete?
  cookie_split_auth_token&.size == 3
end


206
207
208
# File 'app/helpers/camaleon_cms/session_helper.rb', line 206

def cookie_split_auth_token
  cookies[:auth_token]&.split('&')
end

#login_user(user, remember_me = false, redirect_url = nil, rotate_session: true, allow_external: false) ⇒ Object

log in the user in to system user: User model remember_me: true/false (remember session permanently) redirect_url (default nil): after initialized the session, this will be redirected to "redirect_url" if defined it doesn't redirect if redirect_url === false return to previous page if defined the cookie, or login url received extra param: return_to=https://mysite.com allow_external (default false): follow redirect_url even when it points off-site, for a caller that has vetted the destination (e.g. an after_login hook doing SSO/payment). http(s) only; a configured allowlist (redirect_allowed_hosts option / safe_redirect_hosts hook) is honored regardless of it.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'app/helpers/camaleon_cms/session_helper.rb', line 14

def (user, remember_me = false, redirect_url = nil, rotate_session: true, allow_external: false)
  # Rotate the session on a genuine sign-in (H6): a fresh session drops any state left behind by a
  # previous occupant of a shared browser — notably an impersonation parent_auth_token, which
  # session_back_to_parent would otherwise restore on logout, escalating the new user to admin.
  # Impersonation manages that token itself and passes rotate_session: false so this does not wipe it.
  reset_session if rotate_session

  # Security (audit 2026-08-11 M3): the auth cookie is HttpOnly (JavaScript/XSS cannot read the
  # bearer token) and Secure over an SSL request (never sent in the clear). The three components are
  # joined into one string so the split-based reader and the impersonation stash keep their shape.
  c = cama_auth_cookie_options([user.auth_token, request.user_agent, request.ip].join('&'))
  c[:expires] = 1.month.from_now if remember_me

  # fix to overwrite a cookie
  cookies.delete(:auth_token, domain: :all)
  cookies.delete(:auth_token)

  user.update({ last_login_at: Time.zone.now })
  cookies[:auth_token] = c

  # user redirection
  flash[:notice] = t('camaleon_cms.admin.login.message.success', locale: current_site.get_admin_language)
  return if redirect_url == false

  if redirect_url.present?
    # Host-check the explicit redirect_url too: after_login hooks and downstream plugins pass
    # caller-controlled destinations here (e.g. a return_to cookie), so it gets the same open-redirect
    # guard as the return_to cookie branch below. allow_external lets such a hook opt a vetted off-site
    # destination (SSO/payment) past the same-host rule; it stays http(s)-only either way.
    cama_safe_redirect(redirect_url, cama_admin_dashboard_path, allow_external: allow_external)
  elsif (return_to = cookies.delete(:return_to)).present?
    cama_safe_redirect(return_to, cama_admin_dashboard_path)
  else
    redirect_to cama_admin_dashboard_path
  end
end

#login_user_with_password(username, password) ⇒ Object

login a user using username and password return boolean: true => authenticated, false => authentication failed



53
54
55
56
57
58
59
60
# File 'app/helpers/camaleon_cms/session_helper.rb', line 53

def (username, password)
  # Custom class finder: usernames are stored downcased, so the lookup must
  # lower-case both sides regardless of DB collation.
  user = current_site.users.find_by_username(username) # rubocop:disable Rails/DynamicFindBy
  r = { user: user, params: params, password: password, captcha_validate: true }
  hooks_run('user_before_login', r)
  user&.authenticate(password)
end

#session_back_to_parent(redirect_url = nil) ⇒ Object

switch current session into parent session called by session_switch_user after returned into parent session, this will be redirected to redirect_url or admin dashboard SECURITY: only call this once the parent admin has been re-authenticated (see Admin::SessionsController#back_to_parent and cama_impersonation_parent_user).



145
146
147
148
149
150
151
# File 'app/helpers/camaleon_cms/session_helper.rb', line 145

def session_back_to_parent(redirect_url = nil)
  return unless cama_sign_in? && session[:parent_auth_token].present?

  cookies[:auth_token] = cama_auth_cookie_options(session[:parent_auth_token])
  session.delete(:parent_auth_token)
  redirect_to (redirect_url || cama_admin_dashboard_path), notice: 'Welcome back!'
end

#session_switch_user(user, redirect_url = nil) ⇒ Object

switch current session user into other (user) after switched, this will be redirected to redirect_url or admin dashboard



116
117
118
119
120
121
122
123
124
125
126
# File 'app/helpers/camaleon_cms/session_helper.rb', line 116

def session_switch_user(user, redirect_url = nil)
  return unless cama_sign_in?

  # Start impersonation from a clean session (see login_user), then stash the admin's auth cookie.
  # The stash happens AFTER the reset and login_user is told not to rotate again, so the token
  # session_back_to_parent restores later survives this rotation (H6).
  parent_auth_token = cookies[:auth_token]
  reset_session
  session[:parent_auth_token] = parent_auth_token
  (user, false, redirect_url, rotate_session: false)
end


210
211
212
# File 'app/helpers/camaleon_cms/session_helper.rb', line 210

def user_auth_token_from_cookie
  cookie_split_auth_token.first
end