diff --git a/app/controllers/backend/programs_controller.rb b/app/controllers/backend/programs_controller.rb index 6fe1022..0f77c73 100644 --- a/app/controllers/backend/programs_controller.rb +++ b/app/controllers/backend/programs_controller.rb @@ -1,5 +1,5 @@ class Backend::ProgramsController < Backend::ApplicationController - before_action :set_program, only: [ :show, :edit, :update, :destroy ] + before_action :set_program, only: [ :show, :edit, :update, :destroy, :rotate_credentials ] hint :list_navigation, on: :index hint :back_navigation, on: :index @@ -61,6 +61,13 @@ class Backend::ProgramsController < Backend::ApplicationController redirect_to backend_programs_path, notice: "Program was successfully deleted." end + def rotate_credentials + authorize @program + @program.rotate_credentials! + redirect_to backend_program_path(@program), notice: "Credentials have been rotated. Make sure to update any integrations using the old secret/API key." + end + + private def set_program diff --git a/app/controllers/developer_apps_controller.rb b/app/controllers/developer_apps_controller.rb index 001d515..74a486b 100644 --- a/app/controllers/developer_apps_controller.rb +++ b/app/controllers/developer_apps_controller.rb @@ -1,6 +1,6 @@ class DeveloperAppsController < ApplicationController before_action :require_developer_mode - before_action :set_app, only: [ :show, :edit, :update, :destroy ] + before_action :set_app, only: [ :show, :edit, :update, :destroy, :rotate_credentials ] def index @apps = current_identity.owned_developer_apps.order(created_at: :desc) @@ -43,6 +43,11 @@ class DeveloperAppsController < ApplicationController redirect_to developer_apps_path, notice: t(".success"), status: :see_other end + def rotate_credentials + @app.rotate_credentials! + redirect_to developer_app_path(@app), notice: t(".success") + end + private def require_developer_mode diff --git a/app/jobs/identity/reap_aged_out_users_job.rb b/app/jobs/identity/reap_aged_out_users_job.rb index f58d0c5..a9a5be3 100644 --- a/app/jobs/identity/reap_aged_out_users_job.rb +++ b/app/jobs/identity/reap_aged_out_users_job.rb @@ -2,7 +2,7 @@ class Identity::ReapAgedOutUsersJob < ApplicationJob queue_as :default def perform - aged_out = Identity.where(ysws_eligible: true, hq_override: [false, nil]) + aged_out = Identity.where(ysws_eligible: true, hq_override: [ false, nil ]) .where("birthday <= ?", 19.years.ago.to_date) reaped_count = 0 @@ -15,4 +15,4 @@ class Identity::ReapAgedOutUsersJob < ApplicationJob Rails.logger.info "ReapAgedOutUsersJob: marked #{reaped_count} #{"user".pluralize reaped_count} as alumni and ineligible" end -end \ No newline at end of file +end diff --git a/app/models/program.rb b/app/models/program.rb index 5ed85f5..3cbd232 100644 --- a/app/models/program.rb +++ b/app/models/program.rb @@ -95,6 +95,12 @@ class Program < ApplicationRecord onboarding_scenario_class&.new(identity) end + def rotate_credentials! + self.secret = SecureRandom.hex(32) + self.program_key = "prgmk." + SecureRandom.hex(32) + save! + end + def self.find_by_redirect_uri_host(url) return nil if url.blank? begin diff --git a/app/policies/program_policy.rb b/app/policies/program_policy.rb index 67afd57..adb2eee 100644 --- a/app/policies/program_policy.rb +++ b/app/policies/program_policy.rb @@ -15,6 +15,8 @@ class ProgramPolicy < ApplicationPolicy def update_onboarding_scenario? = user&.super_admin? + def rotate_credentials? = user_is_program_manager? + class Scope < Scope def resolve if user.program_manager? || user.super_admin? diff --git a/app/services/analytics_service.rb b/app/services/analytics_service.rb index b7c358d..48a5305 100644 --- a/app/services/analytics_service.rb +++ b/app/services/analytics_service.rb @@ -134,7 +134,7 @@ class AnalyticsService totals.map do |scenario, total| prom = promoted[scenario] || 0 rate = total > 0 ? ((prom.to_f / total) * 100).round(1) : 0 - [scenario || "default", { total: total, promoted: prom, rate: rate }] + [ scenario || "default", { total: total, promoted: prom, rate: rate } ] end.sort_by { |_, v| -v[:total] }.to_h end diff --git a/app/views/backend/programs/show.html.erb b/app/views/backend/programs/show.html.erb index ca2b6d3..9e3e927 100644 --- a/app/views/backend/programs/show.html.erb +++ b/app/views/backend/programs/show.html.erb @@ -60,6 +60,11 @@ + <% if policy(@program).rotate_credentials? %> +
+ <%= link_to "rotate secret & api key", rotate_credentials_backend_program_path(@program), method: :post, data: { confirm: "are you sure? this will invalidate the current secret and api key. any integrations using them will break." }, class: "btn btn-danger" %> +
+ <% end %> <% if @program.redirect_uri.present? %> diff --git a/app/views/developer_apps/show.html.erb b/app/views/developer_apps/show.html.erb index 8dbd84e..0960e86 100644 --- a/app/views/developer_apps/show.html.erb +++ b/app/views/developer_apps/show.html.erb @@ -37,8 +37,13 @@ <% end %> <%= t ".auth_link_hint" %> - - +
+ <%= button_to t(".rotate_credentials"), rotate_credentials_developer_app_path(@app), method: :post, class: "danger small-btn", + form: { "hx-confirm": t(".rotate_confirm") } %> + <%= t ".rotate_hint" %> +
+ +

<%= t ".configuration" %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 4c80a7c..aeb1575 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -157,6 +157,9 @@ en: edit: Edit delete: Delete delete_confirm: Are you sure you want to delete this app? This cannot be undone. + rotate_credentials: Rotate Secret & API Key + rotate_confirm: Are you sure? This will invalidate your current client secret and API key, breaking any existing integrations using those credentials. + rotate_hint: If your credentials have been compromised, you can rotate them to generate new ones. Just make sure to update your integration with the new credentials afterward! client_id: Client ID click_to_copy_client_id: click to copy client ID blank_slate: @@ -183,6 +186,9 @@ en: edit_app: Edit App delete_app: Delete App delete_confirm: Are you sure you want to delete this app? This action cannot be undone and will revoke all existing tokens. + rotate_credentials: Rotate Secret & API Key + rotate_confirm: Are you sure? This will generate a new client secret and API key. The old ones will stop working immediately. + rotate_hint: If your credentials have been compromised, rotate them here. new: title: Create New OAuth App back_to_apps: ← Back to Apps @@ -218,6 +224,8 @@ en: success: OAuth app updated successfully! destroy: success: OAuth app deleted successfully! + rotate_credentials: + success: OAuth app credentials rotated successfully! require_developer_mode: developer_mode_required: Developer mode is not enabled for your account. set_app: @@ -671,4 +679,4 @@ en: delete_confirm: Are you sure you want to delete this address? add_new: Add a new address add_button: Add Address - done: Done \ No newline at end of file + done: Done diff --git a/config/routes.rb b/config/routes.rb index 8cd9e89..9672560 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -226,7 +226,12 @@ Rails.application.routes.draw do end end - resources :programs + resources :programs do + member do + post :rotate_credentials + end + end + post "/break_glass", to: "break_glass#create" @@ -356,7 +361,12 @@ Rails.application.routes.draw do resources :authorized_applications, only: [ :index, :destroy ] - resources :developer_apps, path: "developer/apps" + resources :developer_apps, path: "developer/apps" do + member do + post :rotate_credentials + end + end + namespace :api do namespace :v1 do