From 898f6d52bf3e4c257041f2f9c8f6238bece71f10 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <ynerant@crans.org>
Date: Tue, 15 Jun 2021 21:31:51 +0200
Subject: [PATCH] Better templates for OAuth2 authentication

Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
---
 apps/permission/scopes.py                     |   7 +-
 locale/fr/LC_MESSAGES/django.po               | 142 +++++++++++++++---
 .../application_confirm_delete.html           |  24 +++
 .../oauth2_provider/application_detail.html   |  34 +++++
 .../oauth2_provider/application_form.html     |  30 ++++
 .../oauth2_provider/application_list.html     |  27 ++++
 .../application_registration_form.html        |   9 ++
 .../templates/oauth2_provider/authorize.html  |  40 +++++
 .../oauth2_provider/authorized-oob.html       |  29 ++++
 .../authorized-token-delete.html              |  16 ++
 .../oauth2_provider/authorized-tokens.html    |  27 ++++
 11 files changed, 361 insertions(+), 24 deletions(-)
 create mode 100644 note_kfet/templates/oauth2_provider/application_confirm_delete.html
 create mode 100644 note_kfet/templates/oauth2_provider/application_detail.html
 create mode 100644 note_kfet/templates/oauth2_provider/application_form.html
 create mode 100644 note_kfet/templates/oauth2_provider/application_list.html
 create mode 100644 note_kfet/templates/oauth2_provider/application_registration_form.html
 create mode 100644 note_kfet/templates/oauth2_provider/authorize.html
 create mode 100644 note_kfet/templates/oauth2_provider/authorized-oob.html
 create mode 100644 note_kfet/templates/oauth2_provider/authorized-token-delete.html
 create mode 100644 note_kfet/templates/oauth2_provider/authorized-tokens.html

diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py
index c2084448..bf74cb81 100644
--- a/apps/permission/scopes.py
+++ b/apps/permission/scopes.py
@@ -3,6 +3,7 @@
 
 from oauth2_provider.scopes import BaseScopes
 from member.models import Club
+from note_kfet.middlewares import get_current_request
 
 from .backends import PermissionBackend
 from .models import Permission
@@ -22,14 +23,12 @@ class PermissionScopes(BaseScopes):
     def get_available_scopes(self, application=None, request=None, *args, **kwargs):
         if not application:
             return []
-        user = application.user
         return [f"{p.id}_{p.membership.club.id}"
                 for t in Permission.PERMISSION_TYPES
-                for p in PermissionBackend.get_raw_permissions(user, t[0])]
+                for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
 
     def get_default_scopes(self, application=None, request=None, *args, **kwargs):
         if not application:
             return []
-        user = application.user
         return [f"{p.id}_{p.membership.club.id}"
-                for p in PermissionBackend.get_raw_permissions(user, 'view')]
+                for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index f832c148..f5f26a89 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -540,8 +540,8 @@ msgstr "Taille maximale : 2 Mo"
 msgid "This image cannot be loaded."
 msgstr "Cette image ne peut pas être chargée."
 
-#: apps/member/forms.py:141 apps/member/views.py:101
-#: apps/registration/forms.py:33 apps/registration/views.py:258
+#: apps/member/forms.py:141 apps/member/views.py:100
+#: apps/registration/forms.py:33 apps/registration/views.py:254
 msgid "An alias with a similar name already exists."
 msgstr "Un alias avec un nom similaire existe déjà."
 
@@ -900,7 +900,7 @@ msgid "Account #"
 msgstr "Compte n°"
 
 #: apps/member/templates/member/base.html:48
-#: apps/member/templates/member/base.html:62 apps/member/views.py:58
+#: apps/member/templates/member/base.html:62 apps/member/views.py:59
 #: apps/registration/templates/registration/future_profile_detail.html:48
 #: apps/wei/templates/wei/weimembership_form.html:117
 msgid "Update Profile"
@@ -932,7 +932,7 @@ msgid ""
 "Are you sure you want to lock this note? This will prevent any transaction "
 "that would be performed, until the note is unlocked."
 msgstr ""
-"Êtes-vous sûr de vouloir verrouiller cette note ? Cela empêchera toute "
+"Êtes-vous sûr⋅e de vouloir verrouiller cette note ? Cela empêchera toute "
 "transaction qui devrait être faite, jusqu'à ce qu'elle soit déverrouillée."
 
 #: apps/member/templates/member/base.html:104
@@ -956,7 +956,7 @@ msgstr "Verrouiller de force"
 msgid ""
 "Are you sure you want to unlock this note? Transactions will be re-enabled."
 msgstr ""
-"Êtes-vous sûr de vouloir déverrouiller cette note ? Les transactions seront "
+"Êtes-vous sûr⋅e de vouloir déverrouiller cette note ? Les transactions seront "
 "à nouveau possible."
 
 #: apps/member/templates/member/club_alias.html:10
@@ -1097,7 +1097,7 @@ msgstr "Sauvegarder les changements"
 msgid "Registrations"
 msgstr "Inscriptions"
 
-#: apps/member/views.py:71 apps/registration/forms.py:23
+#: apps/member/views.py:72 apps/registration/forms.py:23
 msgid "This address must be valid."
 msgstr "Cette adresse doit être valide."
 
@@ -1481,6 +1481,9 @@ msgstr "Pas de motif spécifié"
 #: apps/treasury/templates/treasury/sogecredit_detail.html:65
 #: apps/wei/tables.py:74 apps/wei/tables.py:114
 #: apps/wei/templates/wei/weiregistration_confirm_delete.html:31
+#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18
+#: note_kfet/templates/oauth2_provider/application_detail.html:31
+#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:12
 msgid "Delete"
 msgstr "Supprimer"
 
@@ -1490,6 +1493,7 @@ msgstr "Supprimer"
 #: apps/wei/templates/wei/bus_detail.html:20
 #: apps/wei/templates/wei/busteam_detail.html:20
 #: apps/wei/templates/wei/busteam_detail.html:40
+#: note_kfet/templates/oauth2_provider/application_detail.html:30
 msgid "Edit"
 msgstr "Éditer"
 
@@ -1731,7 +1735,7 @@ msgstr "s'applique au club"
 msgid "role permissions"
 msgstr "permissions par rôles"
 
-#: apps/permission/signals.py:63
+#: apps/permission/signals.py:67
 #, python-brace-format
 msgid ""
 "You don't have the permission to change the field {field} on this instance "
@@ -1740,7 +1744,7 @@ msgstr ""
 "Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
 "modèle {app_label}.{model_name}."
 
-#: apps/permission/signals.py:73 apps/permission/views.py:105
+#: apps/permission/signals.py:77 apps/permission/views.py:105
 #, python-brace-format
 msgid ""
 "You don't have the permission to add an instance of model {app_label}."
@@ -1749,7 +1753,7 @@ msgstr ""
 "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}."
 "{model_name}."
 
-#: apps/permission/signals.py:102
+#: apps/permission/signals.py:106
 #, python-brace-format
 msgid ""
 "You don't have the permission to delete this instance of model {app_label}."
@@ -1977,35 +1981,35 @@ msgstr "L'équipe de la Note Kfet."
 msgid "Register new user"
 msgstr "Enregistrer un nouvel utilisateur"
 
-#: apps/registration/views.py:93
+#: apps/registration/views.py:95
 msgid "Email validation"
 msgstr "Validation de l'adresse mail"
 
-#: apps/registration/views.py:95
+#: apps/registration/views.py:97
 msgid "Validate email"
 msgstr "Valider l'adresse e-mail"
 
-#: apps/registration/views.py:137
+#: apps/registration/views.py:141
 msgid "Email validation unsuccessful"
 msgstr "La validation de l'adresse mail a échoué"
 
-#: apps/registration/views.py:148
+#: apps/registration/views.py:152
 msgid "Email validation email sent"
 msgstr "L'email de vérification de l'adresse email a bien été envoyé"
 
-#: apps/registration/views.py:156
+#: apps/registration/views.py:160
 msgid "Resend email validation link"
 msgstr "Renvoyer le lien de validation"
 
-#: apps/registration/views.py:174
+#: apps/registration/views.py:178
 msgid "Pre-registered users list"
 msgstr "Liste des utilisateurs en attente d'inscription"
 
-#: apps/registration/views.py:198
+#: apps/registration/views.py:202
 msgid "Unregistered users"
 msgstr "Utilisateurs en attente d'inscription"
 
-#: apps/registration/views.py:211
+#: apps/registration/views.py:215
 msgid "Registration detail"
 msgstr "Détails de l'inscription"
 
@@ -2225,7 +2229,7 @@ msgstr "Cette facture est verrouillée et ne peut pas être supprimée."
 msgid ""
 "Are you sure you want to delete this invoice? This action can't be undone."
 msgstr ""
-"Êtes-vous sûr de vouloir supprimer cette facture ? Cette action ne pourra "
+"Êtes-vous sûr⋅e de vouloir supprimer cette facture ? Cette action ne pourra "
 "pas être annulée."
 
 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:28
@@ -2863,7 +2867,7 @@ msgid ""
 "Are you sure you want to delete the registration of %(user)s for the WEI "
 "%(wei_name)s? This action can't be undone."
 msgstr ""
-"Êtes-vous sûr de vouloir supprimer l'inscription de %(user)s pour le WEI "
+"Êtes-vous sûr⋅e de vouloir supprimer l'inscription de %(user)s pour le WEI "
 "%(wei_name)s ? Cette action ne pourra pas être annulée."
 
 #: apps/wei/templates/wei/weiregistration_list.html:19
@@ -3127,6 +3131,104 @@ msgstr "Chercher par un attribut tel que le nom …"
 msgid "There is no results."
 msgstr "Il n'y a pas de résultat."
 
+#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8
+msgid "Are you sure to delete the application"
+msgstr ""
+"Êtes-vous sûr⋅e de vouloir supprimer l'application"
+
+#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17
+#: note_kfet/templates/oauth2_provider/authorize.html:27
+msgid "Cancel"
+msgstr "Annuler"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:11
+msgid "Client id"
+msgstr "ID client"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:14
+msgid "Client secret"
+msgstr "Secret client"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:17
+msgid "Client type"
+msgstr "Type de client"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:20
+msgid "Authorization Grant Type"
+msgstr "Type d'autorisation"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:23
+msgid "Redirect Uris"
+msgstr "URIs de redirection"
+
+#: note_kfet/templates/oauth2_provider/application_detail.html:29
+#: note_kfet/templates/oauth2_provider/application_form.html:23
+msgid "Go Back"
+msgstr "Retour en arrière"
+
+#: note_kfet/templates/oauth2_provider/application_form.html:12
+msgid "Edit application"
+msgstr "Modifier l'application"
+
+#: note_kfet/templates/oauth2_provider/application_list.html:7
+msgid "Your applications"
+msgstr "Vos applications"
+
+#: note_kfet/templates/oauth2_provider/application_list.html:18
+msgid "No applications defined"
+msgstr "Pas d'application définie"
+
+#: note_kfet/templates/oauth2_provider/application_list.html:19
+msgid "Click here"
+msgstr "Cliquez ici"
+
+#: note_kfet/templates/oauth2_provider/application_list.html:19
+msgid "if you want to register a new one"
+msgstr "si vous voulez en enregistrer une nouvelle"
+
+#: note_kfet/templates/oauth2_provider/application_list.html:24
+msgid "New Application"
+msgstr "Nouvelle application"
+
+#: note_kfet/templates/oauth2_provider/application_registration_form.html:5
+msgid "Register a new application"
+msgstr "Enregistrer une nouvelle application"
+
+#: note_kfet/templates/oauth2_provider/authorize.html:9
+#: note_kfet/templates/oauth2_provider/authorize.html:28
+msgid "Authorize"
+msgstr "Autoriser"
+
+#: note_kfet/templates/oauth2_provider/authorize.html:14
+msgid "Application requires following permissions:"
+msgstr "L'application requiert les permissions suivantes :"
+
+#: note_kfet/templates/oauth2_provider/authorize.html:35
+#: note_kfet/templates/oauth2_provider/authorized-oob.html:15
+msgid "Error:"
+msgstr "Erreur :"
+
+#: note_kfet/templates/oauth2_provider/authorized-oob.html:13
+msgid "Success"
+msgstr "Succès"
+
+#: note_kfet/templates/oauth2_provider/authorized-oob.html:21
+msgid "Please return to your application and enter this code:"
+msgstr "Merci de retourner à votre application et entrez ce code :"
+
+#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9
+msgid "Are you sure you want to delete this token?"
+msgstr ""
+"Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?"
+
+#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7
+msgid "Tokens"
+msgstr "Jetons"
+
+#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22
+msgid "There are no authorized tokens yet."
+msgstr "Il n'y a pas encore de jeton autorisé."
+
 #: note_kfet/templates/registration/logged_out.html:13
 msgid "Thanks for spending some quality time with the Web site today."
 msgstr "Merci d'avoir utilisé la Note Kfet."
@@ -3168,7 +3270,7 @@ msgid ""
 "password twice so we can verify you typed it in correctly."
 msgstr ""
 "Veuillez entrer votre ancien mot de passe pour des raisons de sécurité, puis "
-"renseigner votre nouveau mot de passe à deux reprises, pour être sûr de "
+"renseigner votre nouveau mot de passe à deux reprises, pour être sûr⋅e de "
 "l'avoir tapé correctement."
 
 #: note_kfet/templates/registration/password_change_form.html:16
diff --git a/note_kfet/templates/oauth2_provider/application_confirm_delete.html b/note_kfet/templates/oauth2_provider/application_confirm_delete.html
new file mode 100644
index 00000000..ec7f19fb
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/application_confirm_delete.html
@@ -0,0 +1,24 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+
+{% block content %}
+    <div class="card">
+        <div class="card-header text-center">
+            <h3>{% trans "Are you sure to delete the application" %} {{ application.name }}?</h3>
+        </div>
+        <div class="card-footer text-center">
+
+            <form method="post" action="{% url 'oauth2_provider:delete' application.pk %}">
+                {% csrf_token %}
+
+                <div class="control-group">
+                    <div class="controls">
+                        <a class="btn btn-secondary btn-large" href="{% url "oauth2_provider:list" %}">{% trans "Cancel" %}</a>
+                        <input type="submit" class="btn btn-large btn-danger" name="allow" value="{% trans "Delete" %}"/>
+                    </div>
+                </div>
+            </form>
+        </div>
+    </div>
+{% endblock content %}
diff --git a/note_kfet/templates/oauth2_provider/application_detail.html b/note_kfet/templates/oauth2_provider/application_detail.html
new file mode 100644
index 00000000..183ac9b8
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/application_detail.html
@@ -0,0 +1,34 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% block content %}
+    <div class="card">
+        <div class="card-header text-center">
+            <h3>{{ application.name }}</h3>
+        </div>
+        <div class="card-body">
+            <dl class="row">
+                <dt class="col-xl-6">{% trans "Client id" %}</dt>
+                <dd class="col-xl-6"><input class="form-control" type="text" value="{{ application.client_id }}" readonly></dd>
+
+                <dt class="col-xl-6">{% trans "Client secret" %}</dt>
+                <dd class="col-xl-6"><input class="form-control" type="text" value="{{ application.client_secret }}" readonly></dd>
+
+                <dt class="col-xl-6">{% trans "Client type" %}</dt>
+                <dd class="col-xl-6">{{ application.client_type }}</dd>
+
+                <dt class="col-xl-6">{% trans "Authorization Grant Type" %}</dt>
+                <dd class="col-xl-6">{{ application.authorization_grant_type }}</dd>
+
+                <dt class="col-xl-6">{% trans "Redirect Uris" %}</dt>
+                <dd class="col-xl-6"><textarea class="form-control" readonly>{{ application.redirect_uris }}</textarea></dd>
+            </dl>
+
+        </div>
+        <div class="card-footer text-center">
+            <a class="btn btn-secondary" href="{% url "oauth2_provider:list" %}">{% trans "Go Back" %}</a>
+            <a class="btn btn-primary" href="{% url "oauth2_provider:update" application.id %}">{% trans "Edit" %}</a>
+            <a class="btn btn-danger" href="{% url "oauth2_provider:delete" application.id %}">{% trans "Delete" %}</a>
+        </div>
+    </div>
+{% endblock content %}
diff --git a/note_kfet/templates/oauth2_provider/application_form.html b/note_kfet/templates/oauth2_provider/application_form.html
new file mode 100644
index 00000000..aa084018
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/application_form.html
@@ -0,0 +1,30 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% load crispy_forms_filters %}
+
+{% block content %}
+    <form class="form-horizontal" method="post" action="{% block app-form-action-url %}{% url 'oauth2_provider:update' application.id %}{% endblock app-form-action-url %}">
+        <div class="card">
+            <div class="card-header text-center">
+                <h3 class="block-center-heading">
+                    {% block app-form-title %}
+                        {% trans "Edit application" %} {{ application.name }}
+                    {% endblock app-form-title %}
+                </h3>
+            </div>
+            <div class="card-body">
+                    {% csrf_token %}
+                    {{ form|crispy }}
+            </div>
+            <div class="card-footer text-center control-group">
+                <div class="controls">
+                    <a class="btn btn-secondary" href="{% block app-form-back-url %}{% url "oauth2_provider:detail" application.id %}{% endblock app-form-back-url %}">
+                        {% trans "Go Back" %}
+                    </a>
+                    <button type="submit" class="btn btn-primary">Save</button>
+                </div>
+            </div>
+        </div>
+    </form>
+{% endblock %}
diff --git a/note_kfet/templates/oauth2_provider/application_list.html b/note_kfet/templates/oauth2_provider/application_list.html
new file mode 100644
index 00000000..1c17879c
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/application_list.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% block content %}
+    <div class="card">
+        <div class="card-header text-center">
+            <h3>{% trans "Your applications" %}</h3>
+        </div>
+        <div class="card-body">
+            {% if applications %}
+                <ul>
+                    {% for application in applications %}
+                        <li><a href="{{ application.get_absolute_url }}">{{ application.name }}</a></li>
+                    {% endfor %}
+                </ul>
+            {% else %}
+                <p>
+                    {% trans "No applications defined" %}.
+                    <a href="{% url 'oauth2_provider:register' %}">{% trans "Click here" %}</a> {% trans "if you want to register a new one" %}.
+                </p>
+            {% endif %}
+        </div>
+        <div class="card-footer text-center">
+            <a class="btn btn-success" href="{% url "oauth2_provider:register" %}">{% trans "New Application" %}</a>
+        </div>
+    </div>
+{% endblock content %}
diff --git a/note_kfet/templates/oauth2_provider/application_registration_form.html b/note_kfet/templates/oauth2_provider/application_registration_form.html
new file mode 100644
index 00000000..c22eca9e
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/application_registration_form.html
@@ -0,0 +1,9 @@
+{% extends "oauth2_provider/application_form.html" %}
+
+{% load i18n %}
+
+{% block app-form-title %}{% trans "Register a new application" %}{% endblock app-form-title %}
+
+{% block app-form-action-url %}{% url 'oauth2_provider:register' %}{% endblock app-form-action-url %}
+
+{% block app-form-back-url %}{% url "oauth2_provider:list" %}"{% endblock app-form-back-url %}
diff --git a/note_kfet/templates/oauth2_provider/authorize.html b/note_kfet/templates/oauth2_provider/authorize.html
new file mode 100644
index 00000000..61348a5d
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/authorize.html
@@ -0,0 +1,40 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% load crispy_forms_filters %}
+
+{% block content %}
+    <div class="card">
+        <div class="card-header text-center">
+            <h3>{% trans "Authorize" %} {{ application.name }} ?</h3>
+        </div>
+        {% if not error %}
+            <form id="authorizationForm" method="post">
+                <div class="card-body">
+                    <p>{% trans "Application requires following permissions:" %}</p>
+                    {% csrf_token %}
+                    {{ form|crispy }}
+
+                    <ul>
+                        {% for scope in scopes_descriptions %}
+                            <li>{{ scope }}</li>
+                        {% endfor %}
+                    </ul>
+                </div>
+                <div class="card-footer text-center">
+                    <div class="control-group">
+                        <div class="controls">
+                            <input type="submit" class="btn btn-large" value="{% trans "Cancel" %}"/>
+                            <input type="submit" class="btn btn-large btn-primary" name="allow" value="{% trans "Authorize" %}"/>
+                        </div>
+                    </div>
+                </div>
+            </form>
+        {% else %}
+            <div class="card-body">
+                <h2>{% trans "Error:" %} {{ error.error }}</h2>
+                <p>{{ error.description }}</p>
+            </div>
+        {% endif %}
+    </div>
+{% endblock %}
\ No newline at end of file
diff --git a/note_kfet/templates/oauth2_provider/authorized-oob.html b/note_kfet/templates/oauth2_provider/authorized-oob.html
new file mode 100644
index 00000000..c0c3a4f8
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/authorized-oob.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+
+{% block title %}
+Success code={{code}}
+{% endblock %}
+
+{% block content %}
+    <div class="card">
+        <h3 class="card-header text-center">
+            {% if not error %}
+               {% trans "Success" %}
+            {% else %}
+                {% trans "Error:" %} {{ error.error }}
+            {% endif %}
+        </h3>
+
+        <div class="card-body">
+            {% if not error %}
+                <p>{% trans "Please return to your application and enter this code:" %}</p>
+
+                <p><code>{{ code }}</code></p>
+            {% else %}
+                <p>{{ error.description }}</p>
+            {% endif %}
+        </div>
+    </div>
+{% endblock %}
diff --git a/note_kfet/templates/oauth2_provider/authorized-token-delete.html b/note_kfet/templates/oauth2_provider/authorized-token-delete.html
new file mode 100644
index 00000000..8d91bf35
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/authorized-token-delete.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% block content %}
+    <div class="card">
+        <form action="" method="post">
+            {% csrf_token %}
+            <h3 class="card-header text-center">
+                {% trans "Are you sure you want to delete this token?" %}
+            </h3>
+            <div class="card-footer text-center">
+                <input type="submit" value="{% trans "Delete" %}" />
+            </div>
+        </form>
+    </div>
+{% endblock %}
diff --git a/note_kfet/templates/oauth2_provider/authorized-tokens.html b/note_kfet/templates/oauth2_provider/authorized-tokens.html
new file mode 100644
index 00000000..3d7f40f2
--- /dev/null
+++ b/note_kfet/templates/oauth2_provider/authorized-tokens.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% load i18n %}
+{% block content %}
+    <div class="card">
+        <h3 class="card-header text-center">
+            {% trans "Tokens" %}
+        </h3>
+        <div class="card-body">
+            <ul>
+            {% for authorized_token in authorized_tokens %}
+                <li>
+                    {{ authorized_token.application }}
+                    (<a href="{% url 'oauth2_provider:authorized-token-delete' authorized_token.pk %}">revoke</a>)
+                </li>
+                <ul>
+                {% for scope_name, scope_description in authorized_token.scopes.items %}
+                    <li>{{ scope_name }}: {{ scope_description }}</li>
+                {% endfor %}
+                </ul>
+            {% empty %}
+                <li>{% trans "There are no authorized tokens yet." %}</li>
+            {% endfor %}
+            </ul>
+        </div>
+    </div>
+{% endblock %}
-- 
GitLab