Skip to content

Commit 44906df

Browse files
committed
Add Api::TeacherInvitationsController#show action
When the user clicks on the link in the teacher invitation email, they will be taken to a page which allows the user to accept the invitation. This page needs to display the name of the school to which they've been invited. Also we need to check that the invitation token is valid for the currently logged-in user - these scenarios as handled with HTTP status codes as follows: * User is not logged in: 401 Unauthorized * Token does not exist: 404 Not Found * Token has expired: 403 Forbidden * Invitation email has changed since token was created: 403 Forbidden * Invitation email does not match current user email: 403 Forbidden * This response includes an error message to distinguish it from the other two 403 Forbidden responses
1 parent 5a8fe6f commit 44906df

File tree

4 files changed

+124
-0
lines changed

4 files changed

+124
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# frozen_string_literal: true
2+
3+
module Api
4+
class TeacherInvitationsController < ApiController
5+
rescue_from ActiveSupport::MessageVerifier::InvalidSignature, with: -> { denied }
6+
7+
before_action :authorize_user
8+
before_action :load_invitation
9+
before_action :ensure_invitation_email_matches_user_email
10+
11+
def show
12+
render :show, formats: [:json], status: :ok
13+
end
14+
15+
private
16+
17+
def load_invitation
18+
@invitation = Invitation.find_by_token_for!(:teacher_invitation, params[:token])
19+
end
20+
21+
def ensure_invitation_email_matches_user_email
22+
return if @invitation.email_address == current_user.email
23+
24+
render json: { error: 'Invitation email does not match user email' }, status: :forbidden
25+
end
26+
end
27+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# frozen_string_literal: true
2+
3+
json.call(
4+
@invitation,
5+
:school_name
6+
)

config/routes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
resources :lessons, only: %i[index create show update destroy] do
5454
post :copy, on: :member, to: 'lessons#create_copy'
5555
end
56+
57+
resources :teacher_invitations, param: :token, only: :show
5658
end
5759

5860
resource :github_webhooks, only: :create, defaults: { formats: :json }
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'Viewing an invitations', type: :request do
6+
include ActiveSupport::Testing::TimeHelpers
7+
8+
let(:user) { create(:user) }
9+
let(:headers) { { Authorization: UserProfileMock::TOKEN } }
10+
11+
context 'when user is not logged in' do
12+
it 'responds 401 Unauthorized' do
13+
get('/api/teacher_invitations/fake-token')
14+
expect(response).to have_http_status(:unauthorized)
15+
end
16+
end
17+
18+
context 'when user is logged in' do
19+
before do
20+
authenticated_in_hydra_as(user)
21+
end
22+
23+
context 'when invitation does not exist' do
24+
let(:invitation) { build(:invitation) }
25+
let!(:token) { invitation.generate_token_for(:teacher_invitation) }
26+
27+
it 'responds 404 Not Found' do
28+
get("/api/teacher_invitations/#{token}", headers:)
29+
expect(response).to have_http_status(:not_found)
30+
end
31+
end
32+
33+
context 'when invitation exists' do
34+
let(:invitation_email) { user.email }
35+
let(:invitation) { create(:invitation, email_address: invitation_email) }
36+
let!(:token) { invitation.generate_token_for(:teacher_invitation) }
37+
38+
context 'when invitation token is not valid because invitation email has changed' do
39+
before do
40+
invitation.update!(email_address: "not-#{invitation.email_address}")
41+
end
42+
43+
it 'responds 403 Forbidden' do
44+
get("/api/teacher_invitations/#{token}", headers:)
45+
expect(response).to have_http_status(:forbidden)
46+
end
47+
end
48+
49+
context 'when invitation token is not valid because token has expired' do
50+
it 'responds 403 Forbidden' do
51+
travel 31.days do
52+
get("/api/teacher_invitations/#{token}", headers:)
53+
end
54+
expect(response).to have_http_status(:forbidden)
55+
end
56+
end
57+
58+
context 'when invitation email does not match user email' do
59+
let(:invitation_email) { "not-#{user.email}" }
60+
61+
it 'responds 403 Forbidden' do
62+
get("/api/teacher_invitations/#{token}", headers:)
63+
expect(response).to have_http_status(:forbidden)
64+
end
65+
66+
it 'includes error message in response' do
67+
get("/api/teacher_invitations/#{token}", headers:)
68+
69+
json = JSON.parse(response.body)
70+
expect(json['error']).to eq('Invitation email does not match user email')
71+
end
72+
end
73+
74+
context 'when invitation token is valid' do
75+
it 'responds 200 OK' do
76+
get("/api/teacher_invitations/#{token}", headers:)
77+
expect(response).to have_http_status(:ok)
78+
end
79+
80+
it 'includes school name in response' do
81+
get("/api/teacher_invitations/#{token}", headers:)
82+
83+
json = JSON.parse(response.body)
84+
expect(json['school_name']).to eq(invitation.school_name)
85+
end
86+
end
87+
end
88+
end
89+
end

0 commit comments

Comments
 (0)