andrew rotate keys (#624)

This commit is contained in:
Echo 2025-11-15 17:57:10 -05:00 committed by GitHub
parent 4466d8d820
commit 0250448b5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 132 additions and 0 deletions

View file

@ -36,6 +36,17 @@ class UsersController < ApplicationController
notice: "Heartbeats & api keys migration started"
end
def rotate_api_key
@user.api_keys.destroy_all
new_api_key = @user.api_keys.create!(name: "Hackatime key")
render json: { token: new_api_key.token }, status: :ok
rescue => e
Rails.logger.error("error rotate #{e.class.name} #{e.message}")
render json: { error: "cant rotate" }, status: :unprocessable_entity
end
def wakatime_setup
api_key = current_user&.api_keys&.last
api_key ||= current_user.api_keys.create!(name: "Wakatime API Key")

View file

@ -0,0 +1,98 @@
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
async rotateKey(event) {
event.preventDefault()
if (!confirm("Are you sure you want to rotate your API key? Your old key will be immediately invalidated and you'll need to update it in all your applications.")) {
return
}
const button = event.currentTarget
const og = button.textContent
button.textContent = "Rotating..."
button.disabled = true
try {
const r = await fetch("/my/settings/rotate_api_key", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": document.querySelector("[name='csrf-token']").content
}
})
const d = await r.json()
if (r.ok && d.token) {
this.m(d.token)
} else {
alert("Failed to rotate API key: " + (d.error || "Unknown error"))
}
} catch (error) {
console.error("Error rotating API key:", error)
alert("Failed to rotate API key. Please try again.")
} finally {
button.textContent = og
button.disabled = false
}
}
m(token) {
const c = `
<div id="api-key-modal" class="fixed inset-0 flex items-center justify-center z-[9999]" style="background-color: rgba(0, 0, 0, 0.5);backdrop-filter: blur(4px);">
<div class="bg-darker rounded-lg p-6 max-w-md w-full mx-4 border border-gray-600">
<h3 class="text-xl font-bold text-white mb-4">🔑 New API Key Generated</h3>
<div class="space-y-4">
<div>
<p class="text-sm text-gray-300 mb-3">
We have gone ahead and invalidated your old API key, here is your new API key. Update your editor configuration with this new key.
</p>
<div class="bg-gray-800 border border-gray-600 rounded p-3">
<code id="new-api-key" class="text-sm text-white break-all">${token}</code>
</div>
</div>
<div class="flex gap-3 pt-2">
<button type="button" id="copy-api-key"
class="flex-1 bg-primary hover:bg-red text-white px-4 py-2 rounded-lg transition-colors">
Copy Key
</button>
<button type="button" id="close-modal"
class="flex-1 bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition-colors">
Close
</button>
</div>
</div>
</div>
</div>
`
document.body.insertAdjacentHTML("beforeend", c)
document.getElementById("copy-api-key").addEventListener("click", () => {
navigator.clipboard.writeText(token).then(() => {
const button = document.getElementById("copy-api-key")
const og = button.textContent
button.textContent = "Copied!"
setTimeout(() => {
button.textContent = og
}, 2000)
})
})
document.getElementById("close-modal").addEventListener("click", this.closeModal)
document.getElementById("api-key-modal").addEventListener("click", (event) => {
if (event.target.id === "api-key-modal") {
this.closeModal()
}
})
}
closeModal() {
const modal = document.getElementById("api-key-modal")
if (modal) {
modal.remove()
}
}
}

View file

@ -361,6 +361,28 @@
</div>
</div>
<div class="border border-primary rounded-xl p-6 bg-dark transition-all duration-200">
<div class="flex items-center gap-3 mb-4">
<div class="p-2 bg-red-600/10 rounded">
<span class="text-2xl">🔑</span>
</div>
<h2 class="text-xl font-semibold text-white" id="user_api_key">API Key</h2>
</div>
<div class="space-y-4">
<p class="text-gray-300 text-sm">
Your API key is used to authenticate requests from your code editor. If your key has been compromised, you can rotate it to generate a new one. Rotating your API key will immediately invalidate your old key. You'll need to update the key in all of your code editors and IDEs.
</p>
<button type="button"
data-controller="api-key-rotation"
data-action="click->api-key-rotation#rotateKey"
class="w-full px-4 py-2 bg-primary hover:bg-red text-white font-medium rounded transition-colors duration-200 cursor-pointer">
Rotate API Key
</button>
</div>
</div>
<%# This is copied from the github thingie blog, Im not good at UI so I copied :) %>
<div class="border border-primary rounded-xl p-6 bg-dark transition-all duration-200 md:col-span-2">

View file

@ -106,6 +106,7 @@ Rails.application.routes.draw do
get "my/settings", to: "users#edit", as: :my_settings
patch "my/settings", to: "users#update"
post "my/settings/migrate_heartbeats", to: "users#migrate_heartbeats", as: :my_settings_migrate_heartbeats
post "my/settings/rotate_api_key", to: "users#rotate_api_key", as: :my_settings_rotate_api_key
namespace :my do
resources :project_repo_mappings, param: :project_name, only: [ :edit, :update ], constraints: { project_name: /.+/ }