refactor(UI api token): add more information to api token list

- network filtering
- validity
- last used

remove creation logic
This commit is contained in:
simon lehericey 2024-01-17 10:44:09 +01:00
parent a23eb80d22
commit b635c940ae
11 changed files with 59 additions and 131 deletions

View file

@ -159,5 +159,13 @@
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM7 11V13H17V11H7Z'%3E%3C/path%3E%3C/svg%3E");
}
}
&-key-line {
&:before,
&:after {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.917 13C12.441 15.8377 9.973 18 7 18C3.68629 18 1 15.3137 1 12C1 8.68629 3.68629 6 7 6C9.973 6 12.441 8.16229 12.917 11H23V13H21V17H19V13H17V17H15V13H12.917ZM7 16C9.20914 16 11 14.2091 11 12C11 9.79086 9.20914 8 7 8C4.79086 8 3 9.79086 3 12C3 14.2091 4.79086 16 7 16Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12.917 13C12.441 15.8377 9.973 18 7 18C3.68629 18 1 15.3137 1 12C1 8.68629 3.68629 6 7 6C9.973 6 12.441 8.16229 12.917 11H23V13H21V17H19V13H17V17H15V13H12.917ZM7 16C9.20914 16 11 14.2091 11 12C11 9.79086 9.20914 8 7 8C4.79086 8 3 9.79086 3 12C3 14.2091 4.79086 16 7 16Z' fill='currentColor'%3E%3C/path%3E%3C/svg%3E");
}
}
// scss-lint:enable VendorPrefix
}

View file

@ -1,22 +1,11 @@
class Profile::APITokenCardComponent < ApplicationComponent
def initialize(created_api_token: nil, created_packed_token: nil)
@created_api_token = created_api_token
@created_packed_token = created_packed_token
end
private
def render?
current_administrateur.present?
end
def api_and_packed_tokens
current_administrateur.api_tokens.order(:created_at).map do |api_token|
if api_token == @created_api_token && @created_packed_token.present?
[api_token, @created_packed_token]
else
[api_token, nil]
end
end
def api_tokens
current_administrateur.api_tokens.order(created_at: :desc)
end
end

View file

@ -2,5 +2,3 @@ en:
tokens_title: API identification tokens
first_paragraph_html: |
These tokens are needed to make calls to the API of %{application_name}. You can <strong>click on </strong><a target=_blank rel="noopener" href="%{api_doc_url}" title="See %{application_name} API documentation">this link</a> to check our documentation
second_paragraph: If you already have applications that use a token and you revoke it, access to the API will be blocked for those applications.
action: Create and display a new token

View file

@ -2,5 +2,3 @@ fr:
tokens_title: Jetons didentification de lAPI (token)
first_paragraph_html: |
Ces jetons sont nécessaires pour effectuer des appels vers lAPI de %{application_name}. Vous pouvez <strong>consulter notre documentation en suivant </strong><a target="_blank" rel="noopener" href="%{api_doc_url}" title="Voir la documentation de l'API de %{application_name}">ce lien</a>
second_paragraph: Si vous avez déjà des applications qui utilisent un jeton et vous le révoquez, laccès à lAPI sera bloqué pour ces applications.
action: Créer et afficher un nouveau jeton

View file

@ -3,15 +3,6 @@
= t('.tokens_title')
%p
= t('.first_paragraph_html', application_name: APPLICATION_NAME, api_doc_url: API_DOC_URL)
%p
= t('.second_paragraph')
= render Dsfr::ListComponent.new do |list|
- api_and_packed_tokens.each do |(api_token, packed_token)|
- list.with_item do
.fr-card.fr-card--horizontal
.fr-card__body.width-100
.fr-card__content
= render Profile::APITokenComponent.new(api_token:, packed_token:)
= button_to t('.action'), api_tokens_path, method: :post, class: "fr-btn fr-btn--secondary"
%ul.fr-mt-4w
= render Profile::APITokenComponent.with_collection(api_tokens)

View file

@ -1,16 +1,32 @@
class Profile::APITokenComponent < ApplicationComponent
def initialize(api_token:, packed_token: nil)
def initialize(api_token:)
@api_token = api_token
@packed_token = packed_token
end
private
def procedures_to_allow_options
@api_token.targetable_procedures.map { ["#{_1.id} #{_1.libelle}", _1.id] }
def recently_used?
@api_token.last_used_at&.> 2.weeks.ago
end
def procedures_to_allow_select_options
{ selected: @api_token.targetable_procedures.first&.id }
def autorizations
right = @api_token.write_access? ? 'lecture et écriture sur' : 'lecture seule sur'
scope = @api_token.full_access? ? 'toutes les démarches' : @api_token.procedures.map(&:libelle).join(', ')
sanitize("#{right} #{tag.b(scope)}")
end
def network_filtering
if @api_token.authorized_networks.present?
"filtrage : #{@api_token.authorized_networks_for_ui}"
else
tag.span('aucun filtrage réseau', class: 'fr-text-default--warning')
end
end
def use_and_expiration
use = @api_token.last_used_at.present? ? "utilisé il y a #{time_ago_in_words(@api_token.last_used_at)} - " : ""
expiration = @api_token.expires_at.present? ? "valable jusquʼau #{l(@api_token.expires_at, format: :long)}" : "valable indéfiniment"
"#{use} #{expiration}"
end
end

View file

@ -1,18 +0,0 @@
en:
allowed_full_access_html: This token has access to <strong>all</strong> the procedures attached to your administrator account
allowed_procedures_html:
zero: This token has no access to <strong>any</strong> process.
one: This token has access to a selected process
other: This token has access to %{count} selected procedures
security_one: For security reasons, it will not be re-posted, please note.
security_two: For security reasons, we can only show it to you when it is created.
prompt_choose_procedure: "-- Please, choose a procedure --"
security_title: "Security options"
action_all: Allow access to all procedures
action_choice: "If you want to grant access to selected procedures only :"
add: Add
delete: Delete
token_procedures: This token has access to the procedures
revoke_token: Revoke token
reading_writing: Reading and writing
reading: Read only

View file

@ -1,18 +0,0 @@
fr:
allowed_full_access_html: Ce jeton a accès à <strong>toutes</strong> les démarches attachées à votre compte administrateur
allowed_procedures_html:
zero: Ce jeton na accès à <strong>aucune</strong> démarche
one: Ce jeton a accès à une démarche sélectionnée
other: Ce jeton a accès à %{count} démarches sélectionnées
security_one: Pour des raisons de sécurité, il ne sera plus ré-affiché, notez-le bien.
security_two: Pour des raisons de sécurité, nous ne pouvons vous lafficher que lors de sa création.
security_title: "Options de sécurité"
action_all: Autoriser laccès a toutes les démarches
prompt_choose_procedure: "-- Veuillez sélectionner une procédure à ajouter --"
action_choice: Si vous souhaitez autoriser laccès seulement a des démarches choisies, ajouter les au jeton
add: Ajouter
delete: Supprimer
token_procedures: Ce jeton a accès aux démarches
revoke_token: Révoquer le jeton
reading_writing: En lecture et écriture
reading: En lecture seule

View file

@ -1,60 +1,16 @@
%h3.fr-card__title
%b= "#{@api_token.name} "
%span.fr-text--sm= @api_token.prefix
.fr-card__desc
- if @packed_token.present?
.fr-text--sm{ style: "width: 80%; word-break: break-all;" }
- button = render Dsfr::CopyButtonComponent.new(text: @packed_token, title: "Copier le jeton dans le presse-papier", success: "Le jeton a été copié dans le presse-papier")
= "#{@packed_token} #{button}"
%p
= t('.security_one')
- else
%p
= t('.security_two')
= render Dsfr::AlertComponent.new(state: :info, title: t(".security_title"), heading_level: :h4) do |c|
- c.with_body do
- if @api_token.full_access?
%p.fr-text--lg
= t('.allowed_full_access_html')
- else
%p.fr-text--lg
= t('.allowed_procedures_html', count: @api_token.procedures.size)
- if @api_token.procedures.empty?
= button_to t('.action_all'), @api_token, method: :patch, params: { api_token: { become_full_access: '1' } }, class: "fr-btn fr-btn--secondary"
- else
%ul
- @api_token.procedures.each do |procedure|
%li.flex.justify-between.align-center
.truncate-80
= "#{procedure.id} #{procedure.libelle}"
= button_to t('.delete'), @api_token, method: :patch, params: { api_token: { disallow_procedure_id: procedure.id } }, class: "fr-btn fr-btn--secondary"
.fr-card__end
= form_for @api_token, namespace: dom_id(@api_token, :allowed_procedures), html: { class: 'mb-3', data: { turbo: true } } do |f|
= f.label :allowed_procedure_ids, class: 'fr-label' do
= t('.action_choice')
- if !@api_token.full_access?
- @api_token.procedures.each do |procedure|
= f.hidden_field :allowed_procedure_ids, value: procedure.id, multiple: true, id: dom_id(procedure, :allowed_procedure)
.flex.justify-between.align-center{ 'data-turbo-force': :server }
= f.select :allowed_procedure_ids, procedures_to_allow_options, {prompt: t('.prompt_choose_procedure')}, { class: 'fr-select ', name: "api_token[allowed_procedure_ids][]" }
= f.button type: :submit, class: "fr-btn fr-btn--secondary" do
= t('.add')
= form_for @api_token, namespace: dom_id(@api_token, :write_access), html: { class: 'form mb-3', data: { turbo: true, controller: 'autosubmit' } } do |f|
= f.label :write_access do
= t('.token_procedures')
%label.toggle-switch.no-margin
= f.check_box :write_access, class: 'toggle-switch-checkbox'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
= t('.reading_writing')
%span.toggle-switch-label.off
= t('.reading')
= button_to t('.revoke_token'), api_token_path(@api_token), method: :delete, class: "fr-btn fr-btn--secondary", data: { turbo_confirm: "Confirmez-vous la révocation de ce jeton ? Les applications qui lutilisent actuellement seront bloquées." }
%li.fr-mt-2w.flex
.fr-mr-4w{ class: class_names('fr-text-default--success': recently_used?) }
%span.fr-icon-key-line
.flex-grow
%div
%b= @api_token.name
%span (commence par #{@api_token.prefix})
%div= autorizations
%div= network_filtering
%div= use_and_expiration
%div
= link_to 'Supprimer',
api_token_path(@api_token),
method: :delete,
class: 'fr-btn fr-btn--tertiary-no-outline fr-btn--sm fr-btn--icon-left fr-icon-delete-line',
data: { confirm: "Confirmez-vous la suppression du jeton « #{@api_token.name} » ?" }

View file

@ -23,7 +23,7 @@ class APITokensController < ApplicationController
def destroy
@api_token.destroy
render :index
redirect_to profil_path
end
private

View file

@ -65,6 +65,10 @@ class APIToken < ApplicationRecord
end
end
def authorized_networks_for_ui
authorized_networks.map { "#{_1.to_string}/#{_1.prefix}" }.join(', ')
end
def forbidden_network?(ip)
return false if authorized_networks.blank?
@ -97,6 +101,10 @@ class APIToken < ApplicationRecord
end
end
def last_used_at
last_v2_authenticated_at || last_v1_authenticated_at
end
private
def sanitize_targeted_procedure_ids