diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 156069f..aee764c 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,14 +1,48 @@ class SessionsController < ApplicationController - def new - redirect_uri = url_for(action: :create, only_path: false) + def hca_new + redirect_uri = url_for(action: :hca_create, only_path: false) + + redirect_to User.hca_authorize_url(redirect_uri), + host: "https://auth.hackclub.com", + allow_other_host: "https://auth.hackclub.com" + end + + def hca_create + if params[:error].present? + Rails.logger.error "Slack OAuth error: #{params[:error]}" + Sentry.capture_message("Slack OAuth error: #{params[:error]}") + redirect_to root_path, alert: "Failed to authenticate with Slack. Error ID: #{Sentry.last_event_id}" + return + end + + redirect_uri = url_for(action: :hca_create, only_path: false) + + @user = User.from_hca_token(params[:code], redirect_uri) + + # if @user&.persisted? + if @user&.persisted? + session[:user_id] = @user.id + + if @user.data_migration_jobs.empty? + MigrateUserFromHackatimeJob.perform_later(@user.id) + end + + redirect_to root_path, notice: "Successfully signed in with Slack! Welcome!" + else + redirect_to root_path, alert: "Failed to authenticate with Hack Club Auth!" + end + end + + def slack_new + redirect_uri = url_for(action: :slack_create, only_path: false) Rails.logger.info "Starting Slack OAuth flow with redirect URI: #{redirect_uri}" - redirect_to User.authorize_url(redirect_uri, close_window: params[:close_window].present?, continue_param: params[:continue]), + redirect_to User.slack_authorize_url(redirect_uri, close_window: params[:close_window].present?, continue_param: params[:continue]), host: "https://slack.com", allow_other_host: "https://slack.com" end - def create - redirect_uri = url_for(action: :create, only_path: false) + def slack_create + redirect_uri = url_for(action: :slack_create, only_path: false) if params[:error].present? Rails.logger.error "Slack OAuth error: #{params[:error]}" diff --git a/app/models/user.rb b/app/models/user.rb index e80e533..a531766 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,7 @@ class User < ApplicationRecord after_create :create_signup_activity before_validation :normalize_username - encrypts :slack_access_token, :github_access_token + encrypts :slack_access_token, :github_access_token, :hca_access_token validates :slack_uid, uniqueness: true, allow_nil: true validates :github_uid, uniqueness: { conditions: -> { where.not(github_access_token: nil) } }, allow_nil: true @@ -327,7 +327,18 @@ class User < ApplicationRecord }) end - def self.authorize_url(redirect_uri, close_window: false, continue_param: nil) + def self.hca_authorize_url(redirect_uri) + params = { + redirect_uri:, + client_id: ENV["HCA_CLIENT_ID"], + response_type: "code", + scope: "email" + } + + URI.parse("#{HCAService.host}/oauth/authorize?#{params.to_query}") + end + + def self.slack_authorize_url(redirect_uri, close_window: false, continue_param: nil) state = { token: SecureRandom.hex(24), close_window: close_window, @@ -355,6 +366,43 @@ class User < ApplicationRecord URI.parse("https://github.com/login/oauth/authorize?#{params.to_query}") end + def self.from_hca_token(code, redirect_uri) + # Exchange code for token + response = HTTP.post("#{HCAService.host}/oauth/token", form: { + client_id: ENV["HCA_CLIENT_ID"], + client_secret: ENV["HCA_CLIENT_SECRET"], + redirect_uri: redirect_uri, + code: code, + grant_type: "authorization_code" + }) + + data = JSON.parse(response.body.to_s) + + access_token = data["access_token"] + return nil if access_token.nil? + + # get user info + identity = ::HCAService.me(access_token) + # find by HCA ID + @user = User.find_by_hca_id(identity["id"]) unless identity["id"].blank? + # find by slack_id + @user ||= User.find_by_slack_uid(identity["slack_id"]) unless identity["slack_id"].blank? + # find by email + @user ||= begin + EmailAddress.find_by(email: identity["email"])&.user unless identity["email"].blank? + end + + # update scopes if user exists + @user.update(hca_scopes: identity["scopes"], hca_id: identity["id"]) if @user + + # if no user, create one + @user ||= begin + u = User.create!(hca_id: identity["id"], slack_uid: identity["slack_id"], hca_scopes: identity["scopes"]) + EmailAddress.create!(email: identity["email"], user: u) unless identity["email"].blank? + u + end + end + def self.from_slack_token(code, redirect_uri) # Exchange code for token response = HTTP.post("https://slack.com/api/oauth.v2.access", form: { diff --git a/app/services/hca_service.rb b/app/services/hca_service.rb new file mode 100644 index 0000000..fba4c8c --- /dev/null +++ b/app/services/hca_service.rb @@ -0,0 +1,18 @@ +module HCAService + def host + if Rails.env.production? + "https://auth.hackclub.com" + else + "https://hca.dinosaurbbq.org" + end + end + + def me(user_token) + raise ArgumentError, "user_token is required" unless user_token + + response = HTTP.auth("Bearer " + user_token) + .get(host + "/api/v1/me") + JSON.parse(response.body) + end + module_function :me, :host +end diff --git a/app/views/static_pages/index.html.erb b/app/views/static_pages/index.html.erb index c004de5..03e50c4 100644 --- a/app/views/static_pages/index.html.erb +++ b/app/views/static_pages/index.html.erb @@ -24,9 +24,16 @@ <% else %>
+ Sign in with Hack Club
+ <% end %>
+ <% end %>
+
<%= link_to slack_auth_path, class: "inline-flex items-center justify-center px-6 py-3 rounded text-white font-bold cursor-pointer border-none w-full my-2 bg-primary" do %>
- Sign in with Hack Club Slack
+ Sign in with Slack
<% end %>