refactor(UI api token): add more information to api token list
- network filtering - validity - last used remove creation logic
This commit is contained in:
parent
a23eb80d22
commit
b635c940ae
11 changed files with 59 additions and 131 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,5 +2,3 @@ fr:
|
|||
tokens_title: Jetons d’identification de l’API (token)
|
||||
first_paragraph_html: |
|
||||
Ces jetons sont nécessaires pour effectuer des appels vers l’API 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, l’accès à l’API sera bloqué pour ces applications.
|
||||
action: Créer et afficher un nouveau jeton
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 n’a 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 l’afficher que lors de sa création.
|
||||
security_title: "Options de sécurité"
|
||||
action_all: Autoriser l’accès a toutes les démarches
|
||||
prompt_choose_procedure: "-- Veuillez sélectionner une procédure à ajouter --"
|
||||
action_choice: Si vous souhaitez autoriser l’accè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
|
|
@ -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 l’utilisent 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} » ?" }
|
||||
|
|
|
@ -23,7 +23,7 @@ class APITokensController < ApplicationController
|
|||
def destroy
|
||||
@api_token.destroy
|
||||
|
||||
render :index
|
||||
redirect_to profil_path
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue