このコミットが含まれているのは:
@@ -10,10 +10,16 @@ RSpec.describe 'Users', type: :request do
|
||||
end
|
||||
|
||||
def auth_headers(user)
|
||||
{ 'X-Transfer-Code' => user.inheritance_code }
|
||||
cookie_headers(user)
|
||||
end
|
||||
|
||||
describe 'GET /users/session' do
|
||||
it 'does not accept the retired transfer-code header' do
|
||||
user = create(:user)
|
||||
get '/users/session', headers: { 'X-Transfer-Code' => user.inheritance_code }
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'requires authentication' do
|
||||
get '/users/session'
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
@@ -251,17 +257,102 @@ RSpec.describe 'Users', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Cookie sessions with CSRF enabled', csrf: true do
|
||||
def csrf_headers
|
||||
get '/users/csrf'
|
||||
expect(response).to have_http_status(:ok)
|
||||
{ 'X-CSRF-Token' => json.fetch('token'), 'Origin' => 'http://www.example.com' }
|
||||
end
|
||||
|
||||
def establish_session(user)
|
||||
post '/users/verify', params: { code: user.inheritance_code }, headers: csrf_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('valid')).to be(true)
|
||||
end
|
||||
|
||||
it 'rejects a write without a token before creating an account' do
|
||||
expect { post '/users' }.not_to change(User, :count)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
expect(response.headers['X-CSRF-Invalid']).to eq('1')
|
||||
end
|
||||
|
||||
it 'uses the encrypted persistent Cookie for CSR and SSR session reads' do
|
||||
user = create(:user)
|
||||
establish_session(user)
|
||||
cookie = Array(response.headers['set-cookie']).join
|
||||
expect(cookie).to include('httponly', 'samesite=lax')
|
||||
expect(cookie).not_to include(user.inheritance_code)
|
||||
expires = Time.httpdate(cookie.match(/expires=([^;]+)/i)[1])
|
||||
expect(expires).to be > 19.years.from_now
|
||||
get '/users/session'
|
||||
expect(json.fetch('id')).to eq(user.id)
|
||||
expect(response.body).not_to include(user.inheritance_code)
|
||||
get '/users/me'
|
||||
expect(json.fetch('inheritance_code')).to eq(user.inheritance_code)
|
||||
expect(response.headers['Cache-Control']).to include('no-store')
|
||||
end
|
||||
|
||||
it 'rejects a cross-origin write with an otherwise valid token' do
|
||||
headers = csrf_headers.merge('Origin' => 'https://other.invalid')
|
||||
expect { post '/users', headers: }.not_to change(User, :count)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'does not replace an existing Cookie identity during recovery' do
|
||||
current = create(:user)
|
||||
old = create(:user)
|
||||
establish_session(current)
|
||||
post '/users/verify', params: { code: old.inheritance_code, restore: true },
|
||||
headers: csrf_headers
|
||||
expect(json.fetch('user').fetch('id')).to eq(current.id)
|
||||
get '/users/session'
|
||||
expect(json.fetch('id')).to eq(current.id)
|
||||
end
|
||||
|
||||
it 'allows explicit transfer and refreshes the active session' do
|
||||
first = create(:user)
|
||||
second = create(:user)
|
||||
establish_session(first)
|
||||
establish_session(second)
|
||||
get '/users/session'
|
||||
expect(json.fetch('id')).to eq(second.id)
|
||||
end
|
||||
|
||||
it 'keeps the renewed session and rejects the previous credential' do
|
||||
user = create(:user)
|
||||
establish_session(user)
|
||||
old_cookie = Array(response.headers['set-cookie']).first.split(';').first
|
||||
old_code = user.inheritance_code
|
||||
post '/users/code/renew', headers: csrf_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('code')).not_to eq(old_code)
|
||||
get '/users/session'
|
||||
expect(json.fetch('id')).to eq(user.id)
|
||||
other_browser = ActionDispatch::Integration::Session.new(Rails.application)
|
||||
other_browser.get('/users/session', headers: { 'Cookie' => old_cookie })
|
||||
expect(other_browser.response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'clears browser authentication on session deletion' do
|
||||
establish_session(create(:user))
|
||||
delete '/users/session', headers: csrf_headers
|
||||
expect(response).to have_http_status(:no_content)
|
||||
get '/users/session'
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /users/me' do
|
||||
it 'returns 404 when code not found' do
|
||||
it 'requires a session even when a code is supplied in the query' do
|
||||
get '/users/me', params: { code: 'nope' }
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns user slice when found' do
|
||||
user = create(:user, inheritance_code: SecureRandom.uuid, name: 'me', role: 'guest')
|
||||
|
||||
get '/users/me', params: { code: user.inheritance_code }
|
||||
get '/users/me', headers: cookie_headers(user)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json['id']).to eq(user.id)
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする