Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# the revoker

<https://revoke.hackclub.com>
API token amnesty box - <https://revoke.hackclub.com>

revoke leaked tokens: paste one in, it gets detected and revoked automatically. the token owner gets notified via slack DM and email.

Expand All @@ -11,6 +11,7 @@ revoke leaked tokens: paste one in, it gets detected and revoked automatically.
- hack club AI API keys
- hack club search API keys
- hackatime admin keys
- hackatime api keys
- HCB OAuth tokens
- slack tokens (xoxb, xoxp, xoxc, xoxd)
- theseus API keys
Expand Down
40 changes: 22 additions & 18 deletions app/models/token_types.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
require_relative "token_types/base"
require_relative "token_types/airtable_pat"
require_relative "token_types/flavortown_api_key"
require_relative "token_types/hack_club_ai_api_key"
require_relative "token_types/hack_club_search_api_key"
require_relative "token_types/hackatime_admin_key"
require_relative "token_types/hack_club_cdn_api_key"
require_relative "token_types/hcb_oauth"
require_relative "token_types/slack_xoxb"
require_relative "token_types/slack_xoxp"
require_relative "token_types/slack_xoxc"
require_relative "token_types/slack_xoxd"
require_relative "token_types/theseus_api_key"
require_relative "token_types/hackatime_token"

module TokenTypes
# Registry of all token types
ALL = [
Expand All @@ -13,29 +28,18 @@ module TokenTypes
SlackXoxc,
SlackXoxd,
TheseusAPIKey,
TheseusPublicAPIKey
TheseusPublicAPIKey,
HackatimeToken
].freeze

def self.find(value)
ALL.find { |token_type| token_type.matches?(value) }
ALL.select { |token_type| token_type.matches?(value) }
end

def self.detect_type(value)
token_type = find(value)
token_type&.display_name || "Unknown"
token_types = find(value)
return "Unknown" if token_types.empty?

token_types.map(&:display_name).join(", ")
end
end

require_relative "token_types/base"
require_relative "token_types/airtable_pat"
require_relative "token_types/flavortown_api_key"
require_relative "token_types/hack_club_ai_api_key"
require_relative "token_types/hack_club_search_api_key"
require_relative "token_types/hackatime_admin_key"
require_relative "token_types/hack_club_cdn_api_key"
require_relative "token_types/hcb_oauth"
require_relative "token_types/slack_xoxb"
require_relative "token_types/slack_xoxp"
require_relative "token_types/slack_xoxc"
require_relative "token_types/slack_xoxd"
require_relative "token_types/theseus_api_key"
57 changes: 57 additions & 0 deletions app/models/token_types/hackatime_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module TokenTypes
class HackatimeToken < Base
self.regex = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
self.name = "Hackatime v2 API key"

def self.revoke(token, **_kwargs)
logger_prefix = "HackatimeToken"
Rails.logger.info("#{logger_prefix}: Starting revocation for token")

hackatime_url = ENV["HACKATIME_API_URL"] || "http://localhost:3000"
auth_token = ENV["HACKATIME_AUTH_TOKEN"]

unless auth_token
Rails.logger.error("#{logger_prefix}: HACKATIME_AUTH_TOKEN not configured")
Sentry.capture_message("HACKATIME_AUTH_TOKEN not configured", level: :error)
return { success: false }
end

begin
connection = Faraday.new(url: hackatime_url) do |faraday|
faraday.request :json
faraday.response :json
end

Rails.logger.info("#{logger_prefix}: Making POST request to #{hackatime_url}/api/internal/revoke")

response = connection.post("/api/internal/revoke", {}, {
"Authorization" => "Bearer #{auth_token}"
}) do |req|
req.params["token"] = token
end

body = response.body
Rails.logger.info("#{logger_prefix}: Response status=#{response.status}, body=#{body.inspect}")

if response.success? && body["success"]
owner_email = body["owner_email"]
key_name = body["key_name"]
status = body["status"]
Rails.logger.info("#{logger_prefix}: Token revoked, owner_email=#{owner_email}, key_name=#{key_name}, status=#{status}")
result = { success: true, owner_email: owner_email }
result[:key_name] = key_name if key_name
result[:status] = status.to_sym if status
result
else
Rails.logger.warn("#{logger_prefix}: API request failed or returned success=false")
{ success: false }
end
rescue StandardError => e
Rails.logger.error("#{logger_prefix}: Exception during revocation - #{e.class}: #{e.message}")
Rails.logger.error(e.backtrace.join("\n"))
Sentry.capture_exception(e)
{ success: false }
end
end
end
end
2 changes: 1 addition & 1 deletion test/controllers/revocations_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class RevocationsControllerTest < ActionDispatch::IntegrationTest
test "should get new" do
get revocations_new_url
get new_revocation_url
assert_response :success
end
end
Loading