module Users class OmniauthCallbacksController < Devise::OmniauthCallbacksController include Devise::Controllers::Rememberable skip_before_action :verify_authenticity_token, only: [:developer] # GET /users/auth/:action/callback def oauth_callback @email = email_from_auth_hash if oauth_origin.present? && oauth_origin[:session_id] if oauth_origin[:link_data].present? passthru_oauth_data elsif @email.blank? redirect_to( oauth_error_url( host: oauth_origin[:fqdn], error: email_blank_flash ), allow_other_host: true ) nil else sign_in_at_oauth_origin end else render "oauth_origin_missing", layout: "error" end end alias google_oauth2 oauth_callback alias facebook oauth_callback alias github oauth_callback alias discord oauth_callback alias developer oauth_callback def failure if oauth_origin.present? message = t(".denied_by", provider: oauth_origin[:provider].capitalize) redirect_to( oauth_error_url(host: oauth_origin[:fqdn], error: message), allow_other_host: true ) else flash[:error] = t(".denied") redirect_to new_user_session_path end end private def oauth_origin @oauth_origin ||= begin raw_origin_data = read_cookie(:oauth_origin) # Parse the JSON format that origin information is stored as. if raw_origin_data.present? # Make sure the cookie isn't reused. cookies.delete :oauth_origin JSON.parse(raw_origin_data, symbolize_names: true) end end end # This method is called when the user is already signed in, and is trying to link their social account with user. def passthru_oauth_data encrypted_token = EncryptorService.new.encrypt( { auth_hash: auth_hash_data, session_id: oauth_origin[:session_id] } ) token_url_options = { encrypted_token: Base64.urlsafe_encode64(encrypted_token), host: oauth_origin[:fqdn] } redirect_to( user_auth_callback_url(**token_url_options), allow_other_host: true ) end # This method is called when the user is not signed in, and is trying to sign in using OAuth. def sign_in_at_oauth_origin if user.present? user.regenerate_login_token encrypted_token = EncryptorService.new.encrypt( { login_token: user.original_login_token, auth_hash: auth_hash_data, session_id: oauth_origin[:session_id] } ) token_url_options = { encrypted_token: Base64.urlsafe_encode64(encrypted_token), host: oauth_origin[:fqdn] } redirect_to( user_auth_callback_url(token_url_options), allow_other_host: true ) else redirect_to( oauth_error_url( host: oauth_origin[:fqdn], error: t( "users.omniauth_callbacks.oauth_callback.email_unregistered", email: @email ) ), allow_other_host: true ) end end def user @user ||= begin school = School .joins(:domains) .where(domains: { fqdn: oauth_origin[:fqdn] }) .first school.users.with_email(@email).first end end # This method is used to pass the auth_hash data to the oauth_origin. def auth_hash_data case auth_hash[:provider] when "google_oauth2", "facebook", "github", "developer" {} when "discord" { discord: { uid: auth_hash[:uid], tag: "#{auth_hash[:extra][:raw_info][:username]}##{auth_hash[:extra][:raw_info][:discriminator]}", access_token: auth_hash[:credentials][:token] } } else raise_unexpected_provider(provider) end end # This is a hack to resolve the issue of flashing message 'You are already signed in' when signing in using OAuth. # For an unknown reason, the request env variable omniauth.origin defaults to the sign in path when no origin is # supplied to the omniauth provider login path. This method detects and removes that default. def origin supplied_origin = request.env["omniauth.origin"] supplied_origin.include?('users/sign_in') ? nil : supplied_origin end # Omniauth returns authentication details in the 'omniauth.auth' request environment variable after the provider # redirects back to our website. The format for this return value is documented by Omniauth. def auth_hash request.env["omniauth.auth"] end # This method validates the format of auth_hash. This ensures that we capture any 'oddities' as crashes, instead of # letting issues get buried (we used to show a useless 404). def email_from_auth_hash raise "Auth hash is blank: #{auth_hash.inspect}" if auth_hash.blank? auth_hash.dig(:info, :email) end def provider_name params[:action].split("_").first.capitalize end def email_blank_flash message = t( "users.omniauth_callbacks.oauth_callback.not_receive_email", provider_name: provider_name ) message += case provider_name when "Github" t("users.omniauth_callbacks.oauth_callback.add_github") when "Facebook" t("users.omniauth_callbacks.oauth_callback.add_facebook") else t("users.omniauth_callbacks.oauth_callback.add_other") end message.html_safe end end end