You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
docuseal/app/controllers/api/v1/admin/invitations_controller.rb

140 lines
4.9 KiB

# frozen_string_literal: true
module Api
module V1
module Admin
# InvitationsController
# Handles admin invitation creation, listing, and revocation
# Implements rate limiting and security event logging
class InvitationsController < ApiBaseController
# Layer 3: Authorization
authorize_resource class: false
# Layer 3: Rate limiting for create action
before_action :check_rate_limit, only: [:create]
before_action :set_institution
before_action :verify_super_admin_access, except: [:index]
# GET /api/v1/admin/invitations
def index
# Layer 1: Scoped query
invitations = CohortAdminInvitation.where(institution: @institution)
# Layer 4: Filter active vs used
if params[:show_used] == 'true'
invitations = invitations.where.not(used_at: nil)
else
invitations = invitations.active
end
render json: {
invitations: invitations.map do |inv|
{
id: inv.id,
email: inv.email,
role: inv.role,
token_preview: inv.token_preview,
sent_at: inv.sent_at,
expires_at: inv.expires_at,
used_at: inv.used_at,
created_by: inv.created_by.email
}
end
}
end
# POST /api/v1/admin/invitations
def create
# Layer 2: Validate role
unless %w[cohort_admin cohort_super_admin].include?(params[:role])
return render json: { error: 'Invalid role specified' }, status: :bad_request
end
# Layer 3: Use InvitationService for business logic
begin
invitation = InvitationService.create_invitation(
@institution,
params[:email],
params[:role],
current_user
)
render json: {
invitation: {
id: invitation.id,
email: invitation.email,
role: invitation.role,
token_preview: invitation.token_preview,
expires_at: invitation.expires_at
},
message: 'Invitation created and email sent'
}, status: :created
rescue RateLimit::LimitApproached => e
render json: { error: e.message }, status: :too_many_requests
rescue CanCan::AccessDenied => e
render json: { error: e.message }, status: :forbidden
rescue ActiveRecord::RecordInvalid => e
render json: { errors: e.record.errors.full_messages }, status: :unprocessable_entity
end
end
# DELETE /api/v1/admin/invitations/:id
def destroy
invitation = CohortAdminInvitation.find_by(id: params[:id], institution: @institution)
unless invitation
return render json: { error: 'Invitation not found' }, status: :not_found
end
# Use service for revocation
if InvitationService.revoke_invitation(invitation, current_user)
render json: { message: 'Invitation revoked successfully' }
else
render json: { error: 'Failed to revoke invitation' }, status: :unprocessable_entity
end
end
private
def set_institution
@institution = Institution.for_user(current_user).find_by(id: params[:institution_id])
unless @institution
log_security_event(:unauthorized_institution_access, {
attempted_institution_id: params[:institution_id],
action: 'manage_invitations'
})
render json: { error: 'Institution not found or access denied' }, status: :not_found
end
end
def verify_super_admin_access
unless current_user.cohort_super_admin? && current_user.managed_institutions.exists?(@institution.id)
log_security_event(:insufficient_privileges, {
action: 'manage_invitations',
institution_id: params[:institution_id],
required_role: 'cohort_super_admin'
})
render json: { error: 'Super admin access required' }, status: :forbidden
end
end
def check_rate_limit
return unless params[:email].present?
if CohortAdminInvitation.rate_limit_check(params[:email], @institution)
log_security_event(:rate_limit_exceeded, {
email: params[:email],
institution_id: @institution.id,
limit: InvitationService::MAX_INVITATIONS_PER_EMAIL
})
render json: {
error: "Maximum #{InvitationService::MAX_INVITATIONS_PER_EMAIL} pending invitations per email"
}, status: :too_many_requests
end
end
end
end
end
end