From 08ad7cf0db158d6677a6c9678a8f48702b5799b9 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 23 Apr 2023 14:10:39 +0200 Subject: [PATCH] Rewrite page for authorized OAuth applications with (Vue)tify --- aleksis/core/frontend/app/dateTimeFormats.js | 14 ++++ .../AuthorizedApplication.vue | 77 +++++++++++++++++++ .../AuthorizedApplications.vue | 40 ++++++++++ .../accessTokens.graphql | 21 +++++ aleksis/core/frontend/messages/en.json | 11 ++- aleksis/core/frontend/routes.js | 10 +-- aleksis/core/urls.py | 4 + 7 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue create mode 100644 aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue create mode 100644 aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql diff --git a/aleksis/core/frontend/app/dateTimeFormats.js b/aleksis/core/frontend/app/dateTimeFormats.js index a835b238c..6a980fc8d 100644 --- a/aleksis/core/frontend/app/dateTimeFormats.js +++ b/aleksis/core/frontend/app/dateTimeFormats.js @@ -19,6 +19,13 @@ const dateTimeFormats = { minute: "numeric", second: "numeric", }, + longNumeric: { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + }, }, de: { short: { @@ -39,6 +46,13 @@ const dateTimeFormats = { minute: "numeric", second: "numeric", }, + longNumeric: { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + }, }, }; diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue new file mode 100644 index 000000000..0fe07455e --- /dev/null +++ b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplication.vue @@ -0,0 +1,77 @@ +<template> + <v-expansion-panel> + <v-expansion-panel-header v-slot="{ open }"> + <div class="d-flex justify-start align-center"> + <v-avatar + x-large + v-if="accessToken.application.icon.absoluteUrl" + class="mr-4" + > + <img + :src="accessToken.application.icon.absoluteUrl" + :alt="accessToken.application.name" + /> + </v-avatar> + <v-avatar x-large v-else class="mr-4" color="secondary"> + <v-icon color="white">mdi-apps</v-icon> + </v-avatar> + <div class="subtitle-1 font-weight-medium"> + {{ accessToken.application.name }} + </div> + </div> + </v-expansion-panel-header> + <v-expansion-panel-content> + <v-list dense class="pa-0"> + <v-list-item> + <v-list-item-content class="body-2"> + {{ + $t("oauth.authorized_application.access_since", { + date: $d(new Date(accessToken.created), "longNumeric"), + }) + }} + · + {{ + $t("oauth.authorized_application.valid_until", { + date: $d(new Date(accessToken.expires), "longNumeric"), + }) + }} + </v-list-item-content> + <v-list-item-action> + <v-btn color="primary"> + {{ $t("oauth.authorized_application.revoke") }} + </v-btn> + </v-list-item-action> + </v-list-item> + <v-list-item v-if="accessToken.scopes && accessToken.scopes.length > 0"> + <div class="pr-4"> + <v-list-item-content class="body-2"> + {{ $t("oauth.authorized_application.has_access_to") }} + </v-list-item-content> + </div> + <v-list dense class="pa-0 flex-grow-1"> + <div v-for="(scope, idx) in accessToken.scopes" :key="scope.name"> + <v-list-item> + <v-list-item-content class="body-2"> + {{ scope.description }} + </v-list-item-content> + </v-list-item> + <v-divider v-if="idx < accessToken.scopes.length - 1" /> + </div> + </v-list> + </v-list-item> + </v-list> + </v-expansion-panel-content> + </v-expansion-panel> +</template> + +<script> +export default { + name: "AuthorizedApplication", + props: { + accessToken: { + type: Object, + required: true, + }, + }, +}; +</script> diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue new file mode 100644 index 000000000..5a6b42102 --- /dev/null +++ b/aleksis/core/frontend/components/authorized_oauth_applications/AuthorizedApplications.vue @@ -0,0 +1,40 @@ +<template> + <div> + <h1 class="mb-4">{{ $t("oauth.authorized_application.title") }}</h1> + <div v-if="$apollo.queries.oauth.loading"> + <v-skeleton-loader type="card"></v-skeleton-loader> + </div> + <div v-else-if="oauth"> + <v-card class="mb-4"> + <v-card-title> + {{ $t("oauth.authorized_application.subtitle") }} + </v-card-title> + <v-card-text> + {{ $t("oauth.authorized_application.description") }} + </v-card-text> + <v-expansion-panels flat> + <authorized-application + v-for="(accessToken, index) in oauth.accessTokens" + :key="accessToken.id" + :access-token="accessToken" + /> + </v-expansion-panels> + </v-card> + </div> + </div> +</template> + +<script> +import gqlAccessTokens from "./accessTokens.graphql"; +import AuthorizedApplication from "./AuthorizedApplication.vue"; + +export default { + name: "AuthorizedApplications", + components: { AuthorizedApplication }, + apollo: { + oauth: { + query: gqlAccessTokens, + }, + }, +}; +</script> diff --git a/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql b/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql new file mode 100644 index 000000000..3c40c63bd --- /dev/null +++ b/aleksis/core/frontend/components/authorized_oauth_applications/accessTokens.graphql @@ -0,0 +1,21 @@ +{ + oauth { + accessTokens { + id + created + updated + expires + scopes { + name + description + } + application { + id + name + icon { + absoluteUrl + } + } + } + } +} diff --git a/aleksis/core/frontend/messages/en.json b/aleksis/core/frontend/messages/en.json index 5235e2326..63af5033d 100644 --- a/aleksis/core/frontend/messages/en.json +++ b/aleksis/core/frontend/messages/en.json @@ -167,8 +167,15 @@ "title": "OAuth Application", "title_plural": "OAuth Applications" }, - "authorized_token": { - "menu_title": "Authorized Applications" + "authorized_application": { + "menu_title": "Third-party Applications", + "title": "Third-party Applications", + "subtitle": "Third-party Applications With Access to Your Account", + "description": "The following third-party applications have access to your account. You can revoke access at any time for those you don't need or trust anymore.", + "valid_until": "Valid until {date}", + "access_since": "Access since {date}", + "has_access_to": "Has access to:", + "revoke": "Revoke Access" } }, "people": "People", diff --git a/aleksis/core/frontend/routes.js b/aleksis/core/frontend/routes.js index 215f4d148..e7fc1e4fe 100644 --- a/aleksis/core/frontend/routes.js +++ b/aleksis/core/frontend/routes.js @@ -920,14 +920,14 @@ const routes = [ }, { path: "/oauth/authorized_tokens/", - component: () => import("./components/LegacyBaseTemplate.vue"), - props: { - byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, - }, + component: () => + import( + "./components/authorized_oauth_applications/AuthorizedApplications.vue" + ), name: "core.oauth.authorizedTokens", meta: { inAccountMenu: true, - titleKey: "oauth.authorized_token.menu_title", + titleKey: "oauth.authorized_application.menu_title", icon: "mdi-gesture-tap-hold", permission: "core.manage_authorized_tokens_rule", }, diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index f5e589906..29fe9dcb2 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -33,6 +33,10 @@ urlpatterns = [ ConnectDiscoveryInfoView.as_view(), name="oidc_configuration", ), + path("oauth/applications/", views.TemplateView.as_view(template_name="core/vue_index.html")), + path( + "oauth/authorized_tokens/", views.TemplateView.as_view(template_name="core/vue_index.html") + ), path("oauth/", include("oauth2_provider.urls", namespace="oauth2_provider")), path( "django/", -- GitLab