diff --git a/app/app/controllers/api/sessions_controller.rb b/app/app/controllers/api/sessions_controller.rb new file mode 100644 index 00000000..8d0d55f5 --- /dev/null +++ b/app/app/controllers/api/sessions_controller.rb @@ -0,0 +1,48 @@ +class Api::SessionsController < ApplicationController + # Skip CSRF protection for API calls if you're using protect_from_forgery + # skip_before_action :verify_authenticity_token, only: [:extend] + + def extend + Rails.logger.info "API::SessionsController#extend called" + + # Reset the Devise timeout timer + request.env['devise.skip_timeout'] = true + + if current_user + Rails.logger.info "Current user found: #{current_user.id}" + current_user.remember_me! if current_user.respond_to?(:remember_me!) + current_user.remember_me = true if current_user.respond_to?(:remember_me=) + sign_in(current_user, force: true) + + respond_to do |format| + format.json { + Rails.logger.info "Responding with JSON" + render json: { success: true, message: "Session extended successfully" } + } + format.html { + Rails.logger.info "Responding with HTML redirect" + redirect_back(fallback_location: root_path) + } + format.any { + Rails.logger.info "Responding with 406 Not Acceptable" + head :not_acceptable + } + end + else + Rails.logger.warn "No current user found" + respond_to do |format| + format.json { render json: { success: false, error: "No active session" }, status: :unauthorized } + format.html { redirect_to new_user_session_path } + format.any { head :unauthorized } + end + end + rescue => e + Rails.logger.error "Error in extend session: #{e.message}" + Rails.logger.error e.backtrace.join("\n") + respond_to do |format| + format.json { render json: { success: false, error: e.message }, status: :internal_server_error } + format.html { redirect_to root_path, alert: "An error occurred while extending your session." } + format.any { head :internal_server_error } + end + end +end \ No newline at end of file diff --git a/app/app/controllers/cbv/sessions_controller.rb b/app/app/controllers/cbv/sessions_controller.rb deleted file mode 100644 index 2b91ef3a..00000000 --- a/app/app/controllers/cbv/sessions_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Cbv - class SessionsController < ApplicationController - def extend - # Reset the Devise timeout timer - request.env['devise.skip_timeout'] = true - current_user.try(:remember_me!) - current_user.try(:remember_me=, true) - sign_in(current_user, force: true) - - respond_to do |format| - format.turbo_stream { render turbo_stream: turbo_stream.remove("session-timeout-modal") } - format.html { redirect_back(fallback_location: root_path) } - end - end - end -end \ No newline at end of file diff --git a/app/app/javascript/controllers/cbv/sessions/timeout_controller.js b/app/app/javascript/controllers/cbv/sessions/timeout_controller.js index 7febce14..e6bbf33f 100644 --- a/app/app/javascript/controllers/cbv/sessions/timeout_controller.js +++ b/app/app/javascript/controllers/cbv/sessions/timeout_controller.js @@ -1,18 +1,15 @@ import { Controller } from "@hotwired/stimulus" +import { sessionAdapter } from "../../../utilities/sessionAdapter" export default class extends Controller { - static targets = ["modal"] + static targets = ["modal", "trigger", "extendButton"] static values = { - warningTime: { type: Number, default: 5000 }, + warningTime: { type: Number, default: 5000 }, // 5 seconds for testing // warningTime: { type: Number, default: 25 * 60 * 1000 }, // 25 minutes in milliseconds timeoutTime: { type: Number, default: 30 * 60 * 1000 } // 30 minutes in milliseconds } connect() { - console.log('Timeout controller connected') - console.log('Warning will show in:', this.warningTimeValue, 'ms') - console.log('Timeout will occur in:', this.timeoutTimeValue, 'ms') - this.createModalTrigger() this.resetTimers() this.addActivityListeners() } @@ -21,12 +18,12 @@ export default class extends Controller { console.log('Timeout controller disconnected') this.clearTimers() this.removeActivityListeners() - this.removeTriggerButton() } resetTimers() { console.log('Resetting timers') this.clearTimers() + console.log('Setting warning timer for', this.warningTimeValue, 'ms') this.warningTimer = setTimeout(() => { console.log('Warning timer triggered') @@ -82,62 +79,36 @@ export default class extends Controller { // Wait 1 second after last activity before resetting timers this.activityTimeout = setTimeout(() => { - console.log('No activity for 1 second, resetting timers') this.resetTimers() }, 1000) } - createModalTrigger() { - // Remove any existing trigger - this.removeTriggerButton() - - // Create the trigger link matching the help link structure - const trigger = document.createElement('a') - trigger.id = 'session-timeout-trigger' - trigger.href = '#cbv-session-timeout-content' - trigger.className = 'usa-button' - trigger.setAttribute('aria-controls', 'cbv-session-timeout-content') - trigger.dataset.openModal = true // Set to true instead of empty string - trigger.textContent = 'Open Session Timeout Modal' - - // Add to DOM in a visible location - const container = document.createElement('div') - container.style.position = 'fixed' - container.style.top = '20px' - container.style.right = '20px' - container.style.zIndex = '9999' - container.appendChild(trigger) - document.body.appendChild(container) - console.log('Created modal trigger:', trigger) - } - - removeTriggerButton() { - const existingTrigger = document.getElementById('session-timeout-trigger') - if (existingTrigger) { - const container = existingTrigger.parentElement - if (container) { - container.remove() - } else { - existingTrigger.remove() - } - console.log('Removed existing trigger button') - } - } - showWarning() { - console.log('Attempting to show warning modal') - const triggerButton = document.getElementById('session-timeout-trigger') - if (!triggerButton) { - console.error('Modal trigger button not found') - return + if (this.hasTriggerTarget) { + this.triggerTarget.click() + } else { + console.error('Trigger target not found when trying to show warning') } - - console.log('Clicking modal trigger button') - triggerButton.click() } timeout() { console.log('Session timed out, redirecting to root') window.location = '/' } + + async extendSession() { + try { + console.log('Calling sessionAdapter.extendSession()') + const response = await sessionAdapter.extendSession() + console.log('Session extended successfully, response:', response) + + // Close the modal + if (this.hasModalTarget) { + console.log('Closing modal') + this.resetTimers() + } + } catch (error) { + console.error('Failed to extend session:', error) + } + } } \ No newline at end of file diff --git a/app/app/javascript/utilities/sessionAdapter.js b/app/app/javascript/utilities/sessionAdapter.js new file mode 100644 index 00000000..d1d3dc08 --- /dev/null +++ b/app/app/javascript/utilities/sessionAdapter.js @@ -0,0 +1,28 @@ +import { fetchInternalAPIService } from './fetchInternalAPIService'; + +export const sessionAdapter = { + /** + * Extends the current user session + * @returns {Promise} The fetch response + */ + extendSession: async () => { + console.log('sessionAdapter.extendSession called'); + try { + console.log('Sending POST request to /api/extend_session'); + + const response = await fetchInternalAPIService('/api/extend_session', { + method: 'POST', + headers: { + 'Accept': 'application/json' + }, + credentials: 'same-origin' + }); + + console.log('Response data:', response); + return response; + } catch (error) { + console.error('Error extending session:', error); + throw error; + } + } +}; \ No newline at end of file diff --git a/app/app/views/cbv/sessions/_timeout_modal.html.erb b/app/app/views/cbv/sessions/_timeout_modal.html.erb index 4e1f2537..8429bfc9 100644 --- a/app/app/views/cbv/sessions/_timeout_modal.html.erb +++ b/app/app/views/cbv/sessions/_timeout_modal.html.erb @@ -1,4 +1,4 @@ -
+

@@ -12,13 +12,35 @@

+
-
\ No newline at end of file +
+ +<%= link_to t('session_timeout.modal.trigger'), "#cbv-session-timeout-modal", { + aria: { controls: "cbv-session-timeout-modal" }, + data: { + open_modal: true, + source: "session-timeout", + cbv_sessions_timeout_target: "trigger" + }, + id: "session-timeout-trigger", + class: "visually-hidden" +}.compact %> diff --git a/app/app/views/layouts/application.html.erb b/app/app/views/layouts/application.html.erb index 8d04ee65..1cc34ea6 100644 --- a/app/app/views/layouts/application.html.erb +++ b/app/app/views/layouts/application.html.erb @@ -54,12 +54,8 @@ <%= render "application/footer" %> - - + <%= render partial: "cbv/sessions/timeout_modal" %> - <%= render partial: "help/help_modal", locals: { help_path: help_path(locale: I18n.locale, r: SecureRandom.hex(4)) } %> diff --git a/app/config/routes.rb b/app/config/routes.rb index a7e5ae4f..4b1bd8f7 100644 --- a/app/config/routes.rb +++ b/app/config/routes.rb @@ -70,6 +70,8 @@ scope :events do post :user_action, to: "user_events#user_action" end + + post '/extend_session', to: 'sessions#extend' end match "/404", to: "pages#error_404", via: :all