From 165af6432782867b4f4139aeb1337ad1a14839fa Mon Sep 17 00:00:00 2001 From: Daniel Tian Date: Fri, 17 Oct 2025 15:57:10 -1000 Subject: [PATCH] Add generate fine-grained personal access tokens page scaffolding --- app/assets/javascripts/access_tokens/index.js | 14 ++++++++++ .../personal_access_tokens/new/index.js | 3 +++ .../generate_token_app.vue | 19 ++++++++++++++ .../personal_access_tokens_controller.rb | 4 +++ .../personal_access_tokens/new.html.haml | 4 +++ .../fine_grained_personal_access_tokens.yml | 10 +++++++ config/routes/user_settings.rb | 2 +- locale/gitlab.pot | 6 +++++ .../personal_access_tokens_controller_spec.rb | 26 +++++++++++++++++++ .../generate_token_app_spec.js | 25 ++++++++++++++++++ 10 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/pages/user_settings/personal_access_tokens/new/index.js create mode 100644 app/assets/javascripts/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue create mode 100644 app/views/user_settings/personal_access_tokens/new.html.haml create mode 100644 config/feature_flags/wip/fine_grained_personal_access_tokens.yml create mode 100644 spec/frontend/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app_spec.js diff --git a/app/assets/javascripts/access_tokens/index.js b/app/assets/javascripts/access_tokens/index.js index 5e3f142543f6c6..26de2ec1973a3e 100644 --- a/app/assets/javascripts/access_tokens/index.js +++ b/app/assets/javascripts/access_tokens/index.js @@ -7,6 +7,7 @@ import { parseRailsFormFields } from '~/lib/utils/forms'; import { __, sprintf } from '~/locale'; import Translate from '~/vue_shared/translate'; import AccessTokens from '~/vue_shared/access_tokens/components/access_tokens.vue'; +import GenerateFineGrainedTokenApp from '~/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue'; import AccessTokenTableApp from './components/access_token_table_app.vue'; import InactiveAccessTokenTableApp from './components/inactive_access_token_table_app.vue'; import ExpiresAtField from './components/expires_at_field.vue'; @@ -204,3 +205,16 @@ export const initTokensApp = () => { }, }); }; + +export const initGenerateFineGrainedTokenApp = () => { + const el = document.getElementById('js-generate-fine-grained-token-app'); + + if (!el) return null; + + return new Vue({ + el, + render(createElement) { + return createElement(GenerateFineGrainedTokenApp); + }, + }); +}; diff --git a/app/assets/javascripts/pages/user_settings/personal_access_tokens/new/index.js b/app/assets/javascripts/pages/user_settings/personal_access_tokens/new/index.js new file mode 100644 index 00000000000000..1a20ad878a1961 --- /dev/null +++ b/app/assets/javascripts/pages/user_settings/personal_access_tokens/new/index.js @@ -0,0 +1,3 @@ +import { initGenerateFineGrainedTokenApp } from '~/access_tokens'; + +initGenerateFineGrainedTokenApp(); diff --git a/app/assets/javascripts/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue b/app/assets/javascripts/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue new file mode 100644 index 00000000000000..fff395309a578b --- /dev/null +++ b/app/assets/javascripts/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/controllers/user_settings/personal_access_tokens_controller.rb b/app/controllers/user_settings/personal_access_tokens_controller.rb index 6bfd86361a040f..7fffaa8fa25611 100644 --- a/app/controllers/user_settings/personal_access_tokens_controller.rb +++ b/app/controllers/user_settings/personal_access_tokens_controller.rb @@ -43,6 +43,10 @@ def index end end + def new + render_404 unless Feature.enabled?(:fine_grained_personal_access_tokens, :instance) + end + def create result = ::PersonalAccessTokens::CreateService.new( current_user: current_user, diff --git a/app/views/user_settings/personal_access_tokens/new.html.haml b/app/views/user_settings/personal_access_tokens/new.html.haml new file mode 100644 index 00000000000000..d8d35af8d7063e --- /dev/null +++ b/app/views/user_settings/personal_access_tokens/new.html.haml @@ -0,0 +1,4 @@ +- page_title s_('AccessTokens|Generate fine-grained token') +- @hide_search_settings = true + +#js-generate-fine-grained-token-app diff --git a/config/feature_flags/wip/fine_grained_personal_access_tokens.yml b/config/feature_flags/wip/fine_grained_personal_access_tokens.yml new file mode 100644 index 00000000000000..14321a087fdbed --- /dev/null +++ b/config/feature_flags/wip/fine_grained_personal_access_tokens.yml @@ -0,0 +1,10 @@ +--- +name: fine_grained_personal_access_tokens +description: Support for fine-grained personal access tokens +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/570705 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209386 +rollout_issue_url: +milestone: '18.6' +group: group::authorization +type: wip +default_enabled: false diff --git a/config/routes/user_settings.rb b/config/routes/user_settings.rb index 39b05bf8a27fcc..9cf6160d38c00d 100644 --- a/config/routes/user_settings.rb +++ b/config/routes/user_settings.rb @@ -13,7 +13,7 @@ put :reset end end - resources :personal_access_tokens, only: [:index, :create] do + resources :personal_access_tokens, only: [:index, :create, :new] do put :toggle_dpop, on: :collection end resources :gpg_keys, only: [:index, :create, :destroy] do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 788302886352f8..f7d03eef9b7239 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3691,6 +3691,9 @@ msgstr "" msgid "AccessTokens|Filter list" msgstr "" +msgid "AccessTokens|Fine-grained personal access tokens give you granular control over the specific resources and actions available to the token." +msgstr "" + msgid "AccessTokens|For example, the application using the token or the purpose of the token." msgstr "" @@ -3703,6 +3706,9 @@ msgstr "" msgid "AccessTokens|Forces new personal, group, project and impersonation tokens to require an expiration date. Does not affect existing token expiration dates or personal access tokens for service accounts. This setting overrides the group-level %{link_start}service account token expiration%{link_end} setting." msgstr "" +msgid "AccessTokens|Generate fine-grained token" +msgstr "" + msgid "AccessTokens|How do I use DPoP?" msgstr "" diff --git a/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb b/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb index ad37df13c7e669..3cb5cd4ed9df41 100644 --- a/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb +++ b/spec/controllers/user_settings/personal_access_tokens_controller_spec.rb @@ -162,4 +162,30 @@ def created_token expect(response.body).to include("SUMMARY:#{format(_("Token '%{name}' expires today"), name: token.name)}") end end + + describe '#new' do + context 'when fine_grained_personal_access_tokens feature flag is disabled' do + before do + stub_feature_flags(fine_grained_personal_access_tokens: false) + end + + it 'returns 404' do + get :new + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when feature flag is enabled' do + before do + stub_feature_flags(fine_grained_personal_access_tokens: true) + end + + it 'renders the new template' do + get :new + + expect(response).to render_template(:new) + end + end + end end diff --git a/spec/frontend/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app_spec.js b/spec/frontend/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app_spec.js new file mode 100644 index 00000000000000..a6e41ca72d0861 --- /dev/null +++ b/spec/frontend/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app_spec.js @@ -0,0 +1,25 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import GenerateTokenApp from '~/vue_shared/access_tokens/components/fine_grained_tokens/generate_token_app.vue'; +import PageHeading from '~/vue_shared/components/page_heading.vue'; + +describe('Generate Token app', () => { + let wrapper; + + const createWrapper = () => { + wrapper = shallowMountExtended(GenerateTokenApp); + }; + + const findPageHeading = () => wrapper.findComponent(PageHeading); + + beforeEach(() => createWrapper()); + + it('shows page header', () => { + expect(findPageHeading().props('heading')).toBe('Generate fine-grained token'); + }); + + it('shows page header description', () => { + expect(findPageHeading().text()).toBe( + 'Fine-grained personal access tokens give you granular control over the specific resources and actions available to the token.', + ); + }); +}); -- GitLab