From dde6f496f66c3f08b26da922d8e25501e94ad67c Mon Sep 17 00:00:00 2001 From: Javi R <4920956+rameerez@users.noreply.github.com> Date: Fri, 23 Jan 2026 01:09:16 +0000 Subject: [PATCH 1/8] Start revamping the dashboard --- app/controllers/api_keys/keys_controller.rb | 9 ++ .../api_keys/security_controller.rb | 8 ++ app/views/api_keys/keys/_key_row.html.erb | 14 +- .../api_keys/keys/_publishable_keys.html.erb | 42 ++++++ app/views/api_keys/keys/_secret_keys.html.erb | 41 ++++++ app/views/api_keys/keys/index.html.erb | 46 ++++++- .../api_keys/security/best_practices.html.erb | 120 +++++++++++------- .../layouts/api_keys/application.html.erb | 56 +++++++- 8 files changed, 274 insertions(+), 62 deletions(-) create mode 100644 app/views/api_keys/keys/_publishable_keys.html.erb create mode 100644 app/views/api_keys/keys/_secret_keys.html.erb diff --git a/app/controllers/api_keys/keys_controller.rb b/app/controllers/api_keys/keys_controller.rb index 781856d..66787ec 100644 --- a/app/controllers/api_keys/keys_controller.rb +++ b/app/controllers/api_keys/keys_controller.rb @@ -4,6 +4,7 @@ module ApiKeys # Controller for managing API keys belonging to the current owner. class KeysController < ApplicationController before_action :set_api_key, only: [:show, :edit, :update, :revoke] + helper_method :key_types_feature_enabled? # GET /keys def index @@ -20,6 +21,14 @@ def index @api_keys = base_scope.active.order(created_at: :desc) # Optionally, fetch inactive ones for a separate section or filter @inactive_api_keys = base_scope.inactive.order(created_at: :desc) + + # When key_types feature is enabled, separate publishable and secret keys + if key_types_feature_enabled? + @publishable_keys = @api_keys.select(&:public_key_type?) + @secret_keys = @api_keys.reject(&:public_key_type?) + @inactive_publishable_keys = @inactive_api_keys.select(&:public_key_type?) + @inactive_secret_keys = @inactive_api_keys.reject(&:public_key_type?) + end end # GET /keys/:id diff --git a/app/controllers/api_keys/security_controller.rb b/app/controllers/api_keys/security_controller.rb index e1c38f7..9601fa2 100644 --- a/app/controllers/api_keys/security_controller.rb +++ b/app/controllers/api_keys/security_controller.rb @@ -6,11 +6,19 @@ class SecurityController < ApplicationController # Skip the user authentication requirement for these static pages # as they contain general information. skip_before_action :authenticate_api_keys_owner!, only: [:best_practices] + helper_method :key_types_feature_enabled? # GET /security/best-practices def best_practices # Renders app/views/api_keys/security/best_practices.html.erb # The view will contain the static content. end + + private + + # Check if key types feature is enabled + def key_types_feature_enabled? + ApiKeys.configuration.key_types.present? && ApiKeys.configuration.key_types.any? + end end end diff --git a/app/views/api_keys/keys/_key_row.html.erb b/app/views/api_keys/keys/_key_row.html.erb index 60d518e..23239f4 100644 --- a/app/views/api_keys/keys/_key_row.html.erb +++ b/app/views/api_keys/keys/_key_row.html.erb @@ -22,14 +22,14 @@ <% end %> - - <%= key.masked_token %> + <% if key.public_key_type? && key.viewable_token.present? %> - - - <%= key.viewable_token %> - - + <%= key.masked_token %> + + + + <% else %> + <%= key.masked_token %> <% end %> diff --git a/app/views/api_keys/keys/_publishable_keys.html.erb b/app/views/api_keys/keys/_publishable_keys.html.erb new file mode 100644 index 0000000..de2e256 --- /dev/null +++ b/app/views/api_keys/keys/_publishable_keys.html.erb @@ -0,0 +1,42 @@ +<%# Partial for displaying publishable keys section %> +<%# Locals: active_keys (Active publishable keys), inactive_keys (Inactive publishable keys) %> + +
+

Publishable Keys

+

+ These keys are safe to embed in client-side applications and browser code. + You can view them anytime. +

+ +
+ <% all_keys = active_keys + inactive_keys %> + <% if all_keys.any? %> + + + + + + + + + + + + + + <% active_keys.each do |key| %> + <%= render partial: 'api_keys/keys/key_row', locals: { key: key } %> + <% end %> + + <% inactive_keys.each do |key| %> + <%= render partial: 'api_keys/keys/key_row', locals: { key: key, inactive: true } %> + <% end %> + +
NameAPI KeyCreatedExpiresLast UsedPermissionsActions
+ <% else %> +
+

No publishable keys yet.

+
+ <% end %> +
+
diff --git a/app/views/api_keys/keys/_secret_keys.html.erb b/app/views/api_keys/keys/_secret_keys.html.erb new file mode 100644 index 0000000..a81d2c3 --- /dev/null +++ b/app/views/api_keys/keys/_secret_keys.html.erb @@ -0,0 +1,41 @@ +<%# Partial for displaying secret keys section %> +<%# Locals: active_keys (Active secret keys), inactive_keys (Inactive secret keys) %> + +
+

Secret Keys

+

+ Keep these private. Never expose in client-side code or share publicly. +

+ +
+ <% all_keys = active_keys + inactive_keys %> + <% if all_keys.any? %> + + + + + + + + + + + + + + <% active_keys.each do |key| %> + <%= render partial: 'api_keys/keys/key_row', locals: { key: key } %> + <% end %> + + <% inactive_keys.each do |key| %> + <%= render partial: 'api_keys/keys/key_row', locals: { key: key, inactive: true } %> + <% end %> + +
NameAPI KeyCreatedExpiresLast UsedPermissionsActions
+ <% else %> +
+

No secret keys yet.

+
+ <% end %> +
+
diff --git a/app/views/api_keys/keys/index.html.erb b/app/views/api_keys/keys/index.html.erb index b976ea4..ef04313 100644 --- a/app/views/api_keys/keys/index.html.erb +++ b/app/views/api_keys/keys/index.html.erb @@ -15,12 +15,44 @@
-

Do not share your API key with others or expose it in the browser or other client-side code. <%= link_to api_keys.security_best_practices_path, class: "text-primary api-keys-align-center" do %> - Learn more  - - <% end %> + <% if key_types_feature_enabled? %> + <% has_publishable_keys = @publishable_keys.any? || @inactive_publishable_keys.any? %> + +

+ <% if has_publishable_keys %> + Secret keys should never be shared or exposed publicly. + Publishable keys can be safely embedded in client applications. + <% else %> + Do not share your API key with others or expose it in the browser or other client-side code. + <% end %> + <%= link_to api_keys.security_best_practices_path, class: "text-primary api-keys-align-center" do %> + Learn more  + + <% end %> +

- <%# Render the reusable table partial %> - <%= render partial: 'keys_table', locals: { active_keys: @api_keys, inactive_keys: @inactive_api_keys } %> + <%# Render secret keys section first (primary use case) %> + <%= render partial: 'secret_keys', locals: { + active_keys: @secret_keys, + inactive_keys: @inactive_secret_keys + } %> + + <%# Only render publishable keys section if there are any %> + <% if has_publishable_keys %> + <%= render partial: 'publishable_keys', locals: { + active_keys: @publishable_keys, + inactive_keys: @inactive_publishable_keys + } %> + <% end %> + + <% else %> +

Do not share your API key with others or expose it in the browser or other client-side code. <%= link_to api_keys.security_best_practices_path, class: "text-primary api-keys-align-center" do %> + Learn more  + + <% end %> + + <%# Render the reusable table partial (legacy mode - single table) %> + <%= render partial: 'keys_table', locals: { active_keys: @api_keys, inactive_keys: @inactive_api_keys } %> + <% end %> -

\ No newline at end of file + diff --git a/app/views/api_keys/security/best_practices.html.erb b/app/views/api_keys/security/best_practices.html.erb index 280648e..48e608f 100644 --- a/app/views/api_keys/security/best_practices.html.erb +++ b/app/views/api_keys/security/best_practices.html.erb @@ -1,70 +1,96 @@
-

API Key Security Best Practices

-

Protecting your API keys is crucial for maintaining the security and integrity of your account and data.

+

API Key Security

+

Protecting your API keys is crucial for maintaining the security of your account and data.

-
+
-
-

1. Treat API Keys Like Passwords

-

Your API keys grant access to your account and potentially sensitive operations. Handle them with the same level of security you would apply to your account password or other critical credentials.

-
+ <% if key_types_feature_enabled? %> +
+

Understanding Key Types

-
-

2. Use Unique Keys for Different Applications & Environments

-

Generate distinct API keys for different applications, services, or integrations that need access. If a key for one application is compromised, you can revoke it without disrupting others. Use separate keys for development, staging, and production environments.

-

Tip: Use the "Name" field when creating keys to easily identify their purpose (e.g., "Production Zapier Integration", "Staging iOS App").

-
+

Secret Keys

+

Secret keys provide full access to your account and should be treated like passwords.

+
    +
  • Never expose in client-side code (browsers, mobile apps, desktop apps)
  • +
  • Never commit to version control (Git, etc.)
  • +
  • Store securely using environment variables or secrets management
  • +
  • Can be revoked immediately if compromised
  • +
-
-

3. Never Expose Keys in Client-Side Code

-

Never embed API keys directly in mobile apps (iOS, Android), browser-side JavaScript, desktop applications, or any code that resides on a user's device. Exposed keys can be easily extracted by malicious actors.

-

Solution: Route API requests through your own backend server. Your server can securely store and use the API key to communicate with the target API on behalf of the client.

-
+

Publishable Keys

+

Publishable keys are designed for client-side use with limited, safe permissions.

+
    +
  • Safe to embed in browser JavaScript, mobile apps, and public code
  • +
  • Limited access — cannot perform sensitive operations
  • +
  • Always visible — you can view the full key anytime in your dashboard
  • +
  • Cannot be revoked — designed to be long-lived identifiers
  • +
+
+ <% end %>
-

4. Never Commit Keys to Version Control (e.g., Git)

-

Committing keys to your source code repository (like Git, Mercurial, etc.) is a common and dangerous mistake. Even in private repositories, accidental pushes or repository breaches can leak your keys.

-

Solution: Store keys in environment variables or use a dedicated secrets management system. Access the key in your code via these secure methods.

-
+

Essential Practices

-
-

5. Securely Store Keys on Your Backend

-
    -
  • Environment Variables: The simplest secure method for many applications. Set an environment variable (e.g., `YOUR_SERVICE_API_KEY`) on your server and access it in your code (e.g., `ENV['YOUR_SERVICE_API_KEY']` in Ruby/Rails).
  • -
  • Secrets Management Services: For more robust needs, especially in production or team environments, use dedicated services like HashiCorp Vault, AWS Secrets Manager, Google Secret Manager, Doppler, etc. These provide encrypted storage, access control, auditing, and often automated rotation capabilities.
  • -
  • Encrypted Configuration Files: If using configuration files, ensure they are encrypted (e.g., Rails encrypted credentials `config/credentials.yml.enc` and `Rails.application.credentials`). <%= link_to "More info here", "https://guides.rubyonrails.org/security.html#custom-credentials", target: "_blank", rel: "noopener noreferrer", class: "text-primary" %>.
  • -
+

Treat Secret Keys Like Passwords

+

Your API keys grant access to your account. Handle them with the same care you would apply to your account password.

+ +

Use Separate Keys for Different Purposes

+

Create distinct keys for different applications and environments. If one key is compromised, you can revoke it without disrupting others.

+

Tip: Use descriptive names like "Production Backend" or "Staging iOS App" to easily identify each key's purpose.

+ + <% unless key_types_feature_enabled? %> +

Never Expose Keys in Client-Side Code

+

Never embed API keys in mobile apps, browser JavaScript, or desktop applications. Exposed keys can be easily extracted by malicious actors.

+

Solution: Route API requests through your own backend server, which can securely store and use the API key.

+ <% end %>
-

6. Implement the Principle of Least Privilege (Scopes)

-

If the API service supports it (and this `api_keys` gem allows for scopes), create keys with only the minimum permissions (scopes) required for their specific task. Avoid using a key with full access if only read access is needed.

-

Note: Scope availability and enforcement depend on how the host application integrates and utilizes the `scopes` attribute provided by this gem.

+

Secure Storage

+ +

Environment Variables

+

The simplest secure method. Set an environment variable on your server:

+
# In your shell or deployment config
+export YOUR_SERVICE_API_KEY="sk_..."
+
+# Access in Ruby
+ENV['YOUR_SERVICE_API_KEY']
+ +

Rails Encrypted Credentials

+

For Rails applications, use encrypted credentials:

+
# Edit credentials
+bin/rails credentials:edit
+
+# Access in code
+Rails.application.credentials.your_service_api_key
+

<%= link_to "Rails Security Guide", "https://guides.rubyonrails.org/security.html#custom-credentials", target: "_blank", rel: "noopener noreferrer", class: "text-primary" %>

+ +

Secrets Management Services

+

For production environments, consider dedicated services like HashiCorp Vault, AWS Secrets Manager, or Google Secret Manager.

-

7. Monitor Usage and Rotate Keys Regularly

-
    -
  • Monitor Usage: Regularly check API usage logs or dashboards (if provided by the service or your monitoring tools). Look for unexpected spikes in activity or requests from unusual locations, which could indicate a compromised key.
  • -
  • Rotate Keys: Periodically generate new keys and revoke old ones (key rotation). This limits the window of opportunity for attackers if a key is ever leaked undetected. How often you rotate depends on your security requirements (e.g., every 90 days, annually). -
    Tip: This dashboard allows creating multiple keys, facilitating rotation. Create a new key, update your application(s), verify they work, and then revoke the old key.
  • -
  • Revoke Immediately if Compromised: If you suspect a key has been leaked or compromised, revoke it immediately using the "Revoke" button on your keys dashboard.
  • -
+

Monitoring & Rotation

+ +

Monitor Usage

+

Regularly check for unexpected activity spikes or requests from unusual locations, which could indicate a compromised key.

+ +

Rotate Keys Periodically

+

Generate new keys and revoke old ones on a regular schedule (e.g., every 90 days). This limits exposure if a key is leaked undetected.

+

Tip: Create a new key first, update your application, verify it works, then revoke the old key.

+ +

Revoke Immediately if Compromised

+

If you suspect a key has been leaked, revoke it immediately from your dashboard.

-

8. Use HTTPS Exclusively

-

Ensure all API requests are made over HTTPS to encrypt the connection and prevent eavesdropping. Transmitting keys over unencrypted HTTP is highly insecure.

+

Always Use HTTPS

+

Ensure all API requests are made over HTTPS. Transmitting keys over unencrypted HTTP exposes them to eavesdropping.


-

By following these best practices, you significantly reduce the risk associated with API key management.

- - <%# Link back to the keys index if appropriate %> - <% if defined?(api_keys.keys_path) %> -

<%= link_to "Back to API Keys", api_keys.keys_path, class: "text-primary" %>

- <% end %> +

<%= link_to "Back to API Keys", api_keys.keys_path, class: "text-primary" %>

-
\ No newline at end of file +
diff --git a/app/views/layouts/api_keys/application.html.erb b/app/views/layouts/api_keys/application.html.erb index 7369ff3..8000811 100644 --- a/app/views/layouts/api_keys/application.html.erb +++ b/app/views/layouts/api_keys/application.html.erb @@ -77,6 +77,38 @@ margin-top: 2.4em; } + /* API Keys sections styling */ + .api-keys-section { + margin-top: 2em; + padding: 1.5em; + border: 1px solid var(--color-grey, #ccc); + border-radius: 8px; + background-color: var(--bg-secondary-color, #f9f9f9); + } + + .api-keys-section h2 { + margin-top: 0; + margin-bottom: 0.5em; + font-size: 1.25em; + } + + .api-keys-section-description { + margin-bottom: 1em; + color: var(--color-darkGrey, #666); + font-size: 0.9em; + } + + .api-keys-info-text { + margin-bottom: 1em; + } + + @media (prefers-color-scheme: dark) { + .api-keys-section { + background-color: var(--bg-secondary-color, rgb(34, 34, 34)); + border-color: var(--color-darkGrey, #555); + } + } +