Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions app/controllers/api/internal/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Api
module Internal
class SessionsController < ApplicationController
include CsrfTokenConcern

prepend_before_action :skip_session_expiration
prepend_before_action :skip_devise_hooks

after_action :add_csrf_token_header_to_response, only: [:update]

respond_to :json

def show
render json: { live: live?, timeout: timeout }
end

def update
analytics.session_kept_alive if live?
update_last_request_at
render json: { live: live?, timeout: timeout }
end

def destroy
analytics.session_timed_out
request_id = sp_session[:request_id]
sign_out
render json: { redirect: root_url(request_id:, timeout: :session) }
end

private

def skip_devise_hooks
request.env['devise.skip_timeout'] = true
request.env['devise.skip_trackable'] = true
end

def live?
timeout.future?
end

def timeout
if last_request_at.present?
Time.zone.at(last_request_at + User.timeout_in)
else
Time.current
end
end

def last_request_at
warden_session['last_request_at'] if warden_session
end

def update_last_request_at
warden_session['last_request_at'] = Time.zone.now.to_i if warden_session
end

def warden_session
session['warden.user.user.session']
end
end
end
end
8 changes: 8 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
post '/api/risc/security_events' => 'risc/security_events#create'
post '/api/irs_attempts_api/security_events' => 'api/irs_attempts_api#create'

namespace :api do
namespace :internal do
get '/sessions' => 'sessions#show'
put '/sessions' => 'sessions#update'
delete '/sessions' => 'sessions#destroy'
end
end

# SAML secret rotation paths
SamlEndpoint.suffixes.each do |suffix|
get "/api/saml/metadata#{suffix}" => 'saml_idp#metadata', format: false
Expand Down
84 changes: 84 additions & 0 deletions spec/controllers/api/internal/sessions_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require 'rails_helper'

RSpec.describe Api::Internal::SessionsController do
let(:user) { nil }

around do |example|
freeze_time { example.run }
end

before do
establish_warden_session if user
end

describe '#show' do
subject(:response) { JSON.parse(get(:show).body, symbolize_names: true) }

it 'responds with live and timeout properties' do
expect(response).to eq(live: false, timeout: Time.zone.now.as_json)
end

context 'signed in' do
let(:user) { create(:user, :signed_up) }

it 'responds with live and timeout properties' do
expect(response).to eq(live: true, timeout: User.timeout_in.from_now.as_json)
end

context 'after a delay' do
let(:delay) { 0.seconds }

before { travel_to delay.from_now }

context 'after a delay prior to session timeout' do
let(:delay) { User.timeout_in - 1.second }

it 'responds with live and timeout properties' do
expect(response).to eq(
live: true,
timeout: (User.timeout_in - delay).from_now.as_json,
)
end
end

context 'after a delay exceeding session timeout' do
let(:delay) { User.timeout_in + 1.second }

it 'responds with live and timeout properties' do
expect(response).to eq(
live: false,
timeout: (User.timeout_in - delay).from_now.as_json,
)
end
end
end

context 'when a request extends session timeout' do
let(:future_time) { (User.timeout_in - 1.second).from_now }

before do
travel_to future_time
# Ideally we could repeat the behavior from `establish_warden_session`, but the request
# and controller persist between simulated request calls.
session['warden.user.user.session']['last_request_at'] = future_time.to_i
end

it 'responds with live and timeout properties' do
expect(response).to eq(live: true, timeout: (future_time + User.timeout_in).as_json)
end
end
end
end

def establish_warden_session
sign_in(user)

# Relevant timeout session values are stored on request, but the API controller itself skips
# these so as not to affect the planned timeout. Send a request to some other controller to
# establish the session values.
original_controller = @controller
@controller = AccountsController.new
get :show
@controller = original_controller
end
end