Add HCA signin (#684)

This commit is contained in:
Max Wofford
2025-12-03 14:05:30 -05:00
committed by GitHub
parent 823d26f418
commit 336ae5c944
8 changed files with 133 additions and 14 deletions

View File

@@ -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]}"

View File

@@ -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: {

View File

@@ -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

View File

@@ -24,9 +24,16 @@
<% else %>
<h1 class="font-bold mt-1 mb-1 text-5xl text-center">Track How Much You <span class="text-primary">Code</span></h1>
<div class="flex flex-col w-full max-w-[50vw] mx-auto mb-22">
<% if params[:hca_auth].present? %>
<%= link_to hca_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 %>
<img src="/images/slack.png" class="h-8 w-8 mr-2 bg-white rounded-full p-1">
<span class="hidden md:flex">Sign in with Hack Club</span>
<% 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 %>
<img src="/images/slack.png" class="h-8 w-8 mr-2 bg-white rounded-full p-1">
<span class="hidden md:flex">Sign in with Hack Club Slack</span>
<span class="hidden md:flex">Sign in with Slack</span>
<% end %>
<div class="flex items-center my-4">