diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index acccf85..0a6bb95 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -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") diff --git a/app/javascript/controllers/api_key_rotation_controller.js b/app/javascript/controllers/api_key_rotation_controller.js new file mode 100644 index 0000000..30c69ce --- /dev/null +++ b/app/javascript/controllers/api_key_rotation_controller.js @@ -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 = ` +
+
+

🔑 New API Key Generated

+
+
+

+ We have gone ahead and invalidated your old API key, here is your new API key. Update your editor configuration with this new key. +

+
+ ${token} +
+
+
+ + +
+
+
+
+ ` + + 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() + } + } +} diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 6572d6a..1855a34 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -361,6 +361,28 @@ +
+
+
+ 🔑 +
+

API Key

+
+ +
+

+ 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. +

+ + +
+
+ <%# This is copied from the github thingie blog, Im not good at UI so I copied :) %>
diff --git a/config/routes.rb b/config/routes.rb index e610ad3..d2f9d46 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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: /.+/ }