From bad1cd9dee2c6644eb742c50b1df8593204d4e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Tue, 23 Dec 2025 22:20:35 +0100 Subject: [PATCH] UX: Improve user API key authorization page (#36777) The authorization page shown when third-party applications request API access was bare-bones and lacked important context for users to make informed decisions. This commit redesigns the page to include: - Clear title with quoted application name - Current user indicator (avatar + username) - Permissions section showing what access is being granted - Redirect destination displayed prominently These changes help users better understand what they're authorizing and where they'll be redirected after approval. Internal ref - t/171051 **BEFORE** CleanShot 2025-12-18 at 16 17 05 **AFTER** 2025-12-18 @ 17 17 43 --- .../stylesheets/common/base/_index.scss | 1 + .../common/base/authorize-api-key.scss | 51 +++++++++++++ app/controllers/user_api_keys_controller.rb | 6 ++ app/views/user_api_keys/new.html.erb | 71 +++++++++++-------- config/locales/server.en.yml | 7 +- spec/system/login_spec.rb | 2 +- 6 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 app/assets/stylesheets/common/base/authorize-api-key.scss diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index 188b81ff12c..55bb290a3c9 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -3,6 +3,7 @@ @import "about"; @import "activation"; @import "alert"; +@import "authorize-api-key"; @import "bbcode"; @import "category-list"; @import "code_highlighting"; diff --git a/app/assets/stylesheets/common/base/authorize-api-key.scss b/app/assets/stylesheets/common/base/authorize-api-key.scss new file mode 100644 index 00000000000..7d204938d5c --- /dev/null +++ b/app/assets/stylesheets/common/base/authorize-api-key.scss @@ -0,0 +1,51 @@ +.authorize-api-key { + max-width: 480px; + margin: 0 auto; + padding: 0 1em; + + &__user { + display: flex; + align-items: center; + gap: 0.35em; + font-size: var(--font-down-1); + color: var(--primary-medium); + margin-bottom: 1.5em; + } + + &__username { + color: var(--primary); + } + + &__permissions { + background-color: var(--primary-very-low); + border-radius: 4px; + padding: 1em; + margin-bottom: 1em; + } + + &__permissions-header { + margin: 0 0 0.5em; + } + + &__scopes { + margin: 0; + padding-left: 1.25em; + } + + &__redirect { + background-color: var(--highlight-bg); + border-radius: 4px; + padding: 1em; + margin-bottom: 1.5em; + } + + &__redirect-url { + margin: 0; + word-break: break-all; + } + + &__buttons { + display: flex; + gap: 0.5em; + } +} diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index 50ff23396d0..9942b0ad485 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -44,6 +44,12 @@ class UserApiKeysController < ApplicationController @nonce = params[:nonce] @client_id = params[:client_id] @auth_redirect = params[:auth_redirect] + @redirect_uri = + begin + URI.parse(@auth_redirect) + rescue StandardError + nil + end if @auth_redirect.present? @push_url = params[:push_url] @localized_scopes = params[:scopes].split(",").map { |s| I18n.t("user_api_key.scopes.#{s}") } @scopes = params[:scopes] diff --git a/app/views/user_api_keys/new.html.erb b/app/views/user_api_keys/new.html.erb index 07e8dab3d72..a0cb6227f0e 100644 --- a/app/views/user_api_keys/new.html.erb +++ b/app/views/user_api_keys/new.html.erb @@ -1,37 +1,46 @@ -

<%= t "user_api_key.title" %>

+

<%= t("user_api_key.title", application_name: @application_name) %>

<% if @no_trust_level %> -

- <%= t("user_api_key.no_trust_level") %> -

+

<%= t("user_api_key.no_trust_level") %>

<% elsif @generic_error %> -

- <%= t("user_api_key.generic_error") %> -

+

<%= t("user_api_key.generic_error") %>

<% else %> -

- <%= t("user_api_key.description", application_name: @application_name) %> -

-

-

-

-<%= form_tag(user_api_key_path) do %> - <%= hidden_field_tag 'application_name', @application_name %> - <%= hidden_field_tag 'access', @access %> - <%= hidden_field_tag 'nonce', @nonce %> - <%= hidden_field_tag 'client_id', @client_id %> - <%= hidden_field_tag('auth_redirect', @auth_redirect) if @auth_redirect %> - <%= hidden_field_tag 'push_url', @push_url %> - <%= hidden_field_tag 'public_key', @public_key%> - <%= hidden_field_tag 'scopes', @scopes%> - <%= hidden_field_tag('padding', @padding) if @padding %> - <%= submit_tag t('user_api_key.authorize'), class: 'btn btn-primary' %> + +
+ <%= t("user_api_key.logged_in_as") %> + " width="24" height="24" alt=""> + <%= current_user.username %> +
+ +
+

<%= t("user_api_key.permissions_header", application_name: @application_name) %>

+
    + <%- @localized_scopes.each do |scope| %> +
  • <%= scope %>
  • + <%- end %> +
+
+ + <% if @redirect_uri %> +
+

<%= t("user_api_key.redirect_warning") %> <%= @redirect_uri.host %><%= ":#{@redirect_uri.port}" if @redirect_uri.port && ![80, 443].include?(@redirect_uri.port) %>

+
+ <% end %> + + <%= form_tag(user_api_key_path) do %> + <%= hidden_field_tag 'application_name', @application_name %> + <%= hidden_field_tag 'access', @access %> + <%= hidden_field_tag 'nonce', @nonce %> + <%= hidden_field_tag 'client_id', @client_id %> + <%= hidden_field_tag('auth_redirect', @auth_redirect) if @auth_redirect %> + <%= hidden_field_tag 'push_url', @push_url %> + <%= hidden_field_tag 'public_key', @public_key %> + <%= hidden_field_tag 'scopes', @scopes %> + <%= hidden_field_tag('padding', @padding) if @padding %> +
+ <%= submit_tag t('user_api_key.authorize'), class: 'btn btn-primary' %> + <%= t('user_api_key.deny') %> +
+ <% end %> <% end %>
-<% end %> - - diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 8781ec2938e..2a86377e42b 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1233,11 +1233,14 @@ en: every_six_months: "every six months" user_api_key: - title: "Authorize application access" + title: 'Authorize "%{application_name}"' authorize: "Authorize" + deny: "Cancel" read: "read" read_write: "read/write" - description: '"%{application_name}" is requesting the following access to your account:' + logged_in_as: "Logged in as" + permissions_header: 'This will allow "%{application_name}" to:' + redirect_warning: "Authorizing will redirect you to" instructions: 'We just generated a new user API key for you to use with "%{application_name}", please paste the following key into your application:' otp_description: 'Would you like to allow "%{application_name}" to access this site?' otp_confirmation: diff --git a/spec/system/login_spec.rb b/spec/system/login_spec.rb index 0280520b9e8..2e1f4e786bf 100644 --- a/spec/system/login_spec.rb +++ b/spec/system/login_spec.rb @@ -288,7 +288,7 @@ shared_examples "login scenarios" do mock_google_auth visit("/user-api-key/new?#{args.to_query}") - expect(page).to have_css(".authorize-api-key .scopes") + expect(page).to have_css(".authorize-api-key__scopes") end it "redirects when navigating to login with redirect param" do