Skip to content

Conversation

@danhalson
Copy link
Contributor

Status

What's changed?

Adds a new endpoint to support google token exchange for the classroom importer

Steps to perform after deploying to production

N/A

@cla-bot cla-bot bot added the cla-signed label Nov 5, 2025
@danhalson danhalson self-assigned this Nov 5, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds a new Google OAuth token exchange endpoint (/api/google/auth/exchange-code) to support the classroom importer feature. The endpoint accepts an authorization code from Google's OAuth flow and exchanges it for access tokens.

Key changes:

  • New controller endpoint for exchanging Google OAuth authorization codes for access tokens
  • Authorization restricted to school teachers and owners (students cannot access)
  • Comprehensive test coverage for success, error, and authorization scenarios

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
app/controllers/api/google_auth_controller.rb New controller implementing the token exchange endpoint with parameter validation and error handling
app/views/api/google_auth/exchange_code.json.jbuilder View template that returns Google's token response fields (access_token, expires_in, token_type, scope, id_token)
config/routes.rb Adds POST route /api/google/auth/exchange-code mapped to the new controller action
app/models/ability.rb Grants exchange_code permission to school teachers and owners for the :google_auth resource
spec/requests/api/google_auth_controller_spec.rb Comprehensive request specs covering successful exchanges, Google API errors, network failures, parameter validation, and authorization
spec/models/ability_spec.rb Tests verifying authorization rules for different user roles (owners, teachers, students, unauthenticated users)
.env.example Adds GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variable examples with documentation reference
app/controllers/auth_controller.rb Removes commented-out code (cleanup, no functional changes)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

conn = Faraday.new do |f|
f.request :url_encoded
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] No timeout configuration for external API call. While Faraday has default timeouts, it's a best practice to explicitly configure timeouts for external API calls to prevent long-running requests from blocking server resources.

Consider adding explicit timeout configuration:

conn = Faraday.new do |f|
  f.request :url_encoded
  f.options.timeout = 10      # connection open timeout
  f.options.open_timeout = 5  # connection initialization timeout
end
Suggested change
f.request :url_encoded
f.request :url_encoded
f.options.timeout = 10 # connection open timeout
f.options.open_timeout = 5 # connection initialization timeout

Copilot uses AI. Check for mistakes.
end

response = conn.post(TOKEN_EXCHANGE_URL, request_body)
@token_response = JSON.parse(response.body)
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for JSON parsing. If Google returns an invalid or malformed JSON response, JSON.parse(response.body) will raise a JSON::ParserError which is not caught by the Faraday::Error rescue block. This could lead to an unhandled exception and a 500 Internal Server Error response to the client.

Consider wrapping the JSON parsing in a rescue block or rescuing JSON::ParserError separately to handle this case gracefully.

Suggested change
@token_response = JSON.parse(response.body)
begin
@token_response = JSON.parse(response.body)
rescue JSON::ParserError => e
render json: { error: "Invalid response from Google: #{e.message}" }, status: :bad_gateway
return
end

Copilot uses AI. Check for mistakes.
'expires_in',
'token_type',
'scope',
'id_token'
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The view does not include refresh_token in the response. Google's OAuth 2.0 token endpoint can return a refresh_token when certain conditions are met (e.g., access_type=offline is requested). If the classroom importer needs to maintain long-term access to Google APIs, the refresh token will be needed.

Consider whether refresh_token should be included in the response, or add a comment explaining why it's intentionally excluded if it's not needed for this use case.

Suggested change
'id_token'
'id_token',
'refresh_token'

Copilot uses AI. Check for mistakes.
if response.success?
render :exchange_code, status: :ok
else
render json: { error: @token_response['error_description'] }, status: :unauthorized
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential nil error message. If Google returns an error response without an error_description field, @token_response['error_description'] will be nil, resulting in { error: nil } being returned to the client. This could be confusing for API consumers.

Consider providing a fallback error message or using @token_response['error'] as a fallback if error_description is not present.

Suggested change
render json: { error: @token_response['error_description'] }, status: :unauthorized
error_message = @token_response['error_description'] || @token_response['error'] || 'Unknown error'
render json: { error: error_message }, status: :unauthorized

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants