|
# app/controllers/github/authorizations_controller.rb |
|
module GitHub |
|
class AuthorizationsController < ApplicationController |
|
allow_unauthenticated_access only: :create |
|
before_action :resume_session, only: :show |
|
|
|
rescue_from ActionController::InvalidAuthenticityToken do |
|
redirect_to root_path, alert: "Authentication with GitHub failed: invalid state token" |
|
end |
|
rescue_from ActionController::ParameterMissing do |
|
redirect_to root_path, alert: "Authentication with GitHub failed: invalid code token" |
|
end |
|
|
|
# see: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps |
|
AUTHORIZE_URI = URI("https://github.com/login/oauth/authorize") |
|
AUTHORIZE_SCOPES = "read:user user:email" |
|
TOKEN_URI = URI("https://github.com/login/oauth/access_token") |
|
USER_INFO_URI = URI("https://api.github.com/user") |
|
CLIENT_ID = Rails.application.credentials.github.oauth.client_id |
|
CLIENT_SECRET = Rails.application.credentials.github.oauth.client_secret |
|
|
|
# POST /github/authorization |
|
def create |
|
store_origin |
|
store_destination |
|
|
|
redirect_to authorize_url, allow_other_host: true |
|
end |
|
|
|
# GET /github/authorization?code=String&state=String |
|
def show |
|
validate_state_token! |
|
details = gather_authorization_details! |
|
user_attributes = { |
|
avatar_url: details.dig("info", "avatar_url"), |
|
github_username: details.dig("info", "login"), |
|
twitter_username: details.dig("info", "twitter_username") |
|
} |
|
|
|
if (user = User.find_by(github_uid: details["uid"])).present? |
|
user.update(user_attributes) |
|
sign_in(user:) |
|
redirect_to after_oauth_path, |
|
notice: "Successfully updated GitHub account" |
|
elsif Current.user.present? |
|
Current.user.update( |
|
github_uid: details["uid"], |
|
**user_attributes |
|
) |
|
|
|
redirect_to after_oauth_path, |
|
notice: "Successfully connected GitHub account" |
|
else |
|
user = User.new( |
|
github_uid: details["uid"], |
|
**user_attributes |
|
) |
|
|
|
if user.save |
|
sign_in(user:) |
|
redirect_to after_oauth_path, |
|
notice: "Signed in successfully via GitHub" |
|
else |
|
redirect_to new_session_path, |
|
alert: "Authentication with GitHub failed: invalid user details" |
|
end |
|
end |
|
rescue ApplicationClient::Error => error |
|
error_response = JSON.parse(error.message, symbolize_names: true) |
|
|
|
redirect_back fallback_location: new_session_path, |
|
alert: error_response[:message] |
|
end |
|
|
|
private |
|
|
|
def store_origin |
|
return unless params.key?(:origin) |
|
|
|
session["oauth.origin"] = params[:origin] |
|
end |
|
|
|
def store_destination |
|
return unless params.key?(:destination) |
|
|
|
session["oauth.destination"] = params[:destination] |
|
end |
|
|
|
def authorize_url |
|
AUTHORIZE_URL.tap do |uri| |
|
uri.query = Rack::Utils.build_query({ |
|
client_id: CLIENT_ID, |
|
redirect_uri: callback_url, |
|
response_type: "code", |
|
scope: AUTHORIZE_SCOPES, |
|
state: form_authenticity_token # prevent CSRF |
|
}) |
|
end.to_s |
|
end |
|
|
|
def validate_state_token! |
|
state_token = params.fetch(:state, nil) |
|
unless valid_authenticity_token?(session, state_token) |
|
raise ActionController::InvalidAuthenticityToken, "The state=#{state_token} token is inauthentic." |
|
end |
|
end |
|
|
|
def gather_authorization_details! |
|
access_credentials = request_access_credentials! |
|
user_info = request_user_info!(access_token: access_credentials.access_token) |
|
|
|
{ |
|
"provider" => :github, |
|
"uid" => user_info.id, |
|
"credentials" => access_credentials.parsed_body.as_json["table"], |
|
"info" => user_info.parsed_body.as_json["table"] |
|
} |
|
end |
|
|
|
def request_access_credentials! |
|
client = ApplicationClient.new |
|
client.post(TOKEN_URI, body: { |
|
client_id: CLIENT_ID, |
|
client_secret: CLIENT_SECRET, |
|
code: params.fetch(:code), |
|
grant_type: "authorization_code", |
|
redirect_uri: callback_url |
|
}) |
|
end |
|
|
|
def request_user_info!(access_token:) |
|
client = ApplicationClient.new(token: access_token) |
|
client.get(USER_INFO_URI) |
|
end |
|
|
|
def callback_url |
|
url_for(action: :show, only_path: false) |
|
end |
|
|
|
def after_oauth_path |
|
session.delete("oauth.destination") || user_root_path |
|
end |
|
end |
|
end |