From f77b0cde907561ecea8835d4f51e01bce87437e7 Mon Sep 17 00:00:00 2001
From: Maxim Filippov <colixer@gmail.com>
Date: Tue, 19 Nov 2019 20:15:15 +0900
Subject: [PATCH] Confirm user account, resend confirmation email

---
 src/api/users.js                              | 20 +++++++++++
 src/lang/en.js                                |  9 ++++-
 src/store/modules/users.js                    | 29 ++++++++++++++-
 .../users/components/MultipleUsersMenu.vue    | 35 +++++++++++++++++++
 src/views/users/index.vue                     | 22 ++++++++++++
 5 files changed, 113 insertions(+), 2 deletions(-)

diff --git a/src/api/users.js b/src/api/users.js
index fb168d6c..3755ee7c 100644
--- a/src/api/users.js
+++ b/src/api/users.js
@@ -136,4 +136,24 @@ export async function fetchUserStatuses(id, authHost, godmode, token) {
   })
 }
 
+export async function confirmUserEmail(nicknames, authHost, token) {
+  return await request({
+    baseURL: baseName(authHost),
+    url: '/api/pleroma/admin/users/confirm_email',
+    method: 'patch',
+    headers: authHeaders(token),
+    data: { nicknames }
+  })
+}
+
+export async function resendConfirmationEmail(nicknames, authHost, token) {
+  return await request({
+    baseURL: baseName(authHost),
+    url: '/api/pleroma/admin/users/resend_confirmation_email',
+    method: 'patch',
+    headers: authHeaders(token),
+    data: { nicknames }
+  })
+}
+
 const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}
diff --git a/src/lang/en.js b/src/lang/en.js
index 29ba5eb3..dd3326e5 100644
--- a/src/lang/en.js
+++ b/src/lang/en.js
@@ -175,6 +175,7 @@ export default {
     external: 'external',
     deactivated: 'deactivated',
     active: 'active',
+    unconfirmed: 'unconfirmed',
     actions: 'Actions',
     activate: 'Activate',
     deactivate: 'Deactivate',
@@ -213,6 +214,8 @@ export default {
     addTagForMultipleUsersConfirmation: 'Are you sure you want to apply tag to all selected users?',
     removeTagFromMultipleUsersConfirmation: 'Are you sure you want to remove tag from all selected users?',
     requirePasswordResetConfirmation: 'Are you sure you want to require password reset for all selected users?',
+    confirmAccountsConfirmation: 'Are you sure you want to confirm emails for all selected users?',
+    resendEmailConfirmation: 'Are you sure you want to resend confirmation email for all selected users?',
     mailerMustBeEnabled: 'To require user\'s password reset you must enable mailer.',
     ok: 'Okay',
     completed: 'Completed',
@@ -230,7 +233,11 @@ export default {
     invalidNicknameError: 'Username can include "a-z", "A-Z" and "0-9" characters',
     getPasswordResetToken: 'Get password reset token',
     passwordResetTokenCreated: 'Password reset token was created',
-    accountCreated: 'New account was created!'
+    accountCreated: 'New account was created!',
+    unconfirmedEmail: 'User didn\'t confirm the email',
+    confirmAccount: 'Confirm account',
+    confirmAccounts: 'Confirm accounts',
+    resendConfirmation: 'Resend confirmation email'
   },
   statuses: {
     statuses: 'Statuses',
diff --git a/src/store/modules/users.js b/src/store/modules/users.js
index 0290b888..160b4ff2 100644
--- a/src/store/modules/users.js
+++ b/src/store/modules/users.js
@@ -12,7 +12,9 @@ import {
   searchUsers,
   tagUser,
   untagUser,
-  requirePasswordReset
+  requirePasswordReset,
+  confirmUserEmail,
+  resendConfirmationEmail
 } from '@/api/users'
 
 const users = {
@@ -151,6 +153,31 @@ const users = {
       }
       dispatch('SuccessMessage')
     },
+    async ConfirmUsersEmail({ commit, dispatch, getters, state }, users) {
+      const updatedUsers = users.map(user => {
+        return { ...user, confirmation_pending: false }
+      })
+      commit('SWAP_USERS', updatedUsers)
+
+      const usersNicknames = users.map(user => user.nickname)
+      try {
+        await confirmUserEmail(usersNicknames, getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      } finally {
+        dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage })
+      }
+      dispatch('SuccessMessage')
+    },
+    async ResendConfirmationEmail({ dispatch, getters }, users) {
+      const usersNicknames = users.map(user => user.nickname)
+      try {
+        await resendConfirmationEmail(usersNicknames, getters.authHost, getters.token)
+      } catch (_e) {
+        return
+      }
+      dispatch('SuccessMessage')
+    },
     async DeleteRight({ commit, dispatch, getters, state }, { users, right }) {
       const updatedUsers = users.map(user => {
         return user.local ? { ...user, roles: { ...user.roles, [right]: false }} : user
diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue
index f3be52cf..1fdc029b 100644
--- a/src/views/users/components/MultipleUsersMenu.vue
+++ b/src/views/users/components/MultipleUsersMenu.vue
@@ -26,6 +26,15 @@
         @click.native="revokeRightFromMultipleUsers('moderator')">
         {{ $t('users.revokeModerator') }}
       </el-dropdown-item>
+      <el-dropdown-item
+        divided
+        @click.native="confirmAccountsForMultipleUsers">
+        {{ $t('users.confirmAccounts') }}
+      </el-dropdown-item>
+      <el-dropdown-item
+        @click.native="resendConfirmationForMultipleUsers">
+        {{ $t('users.resendConfirmation') }}
+      </el-dropdown-item>
       <el-dropdown-item
         divided
         @click.native="activateMultipleUsers">
@@ -209,6 +218,18 @@ export default {
           const filtered = this.selectedUsers.filter(user => user.local)
           filtered.map(user => this.$store.dispatch('RequirePasswordReset', user))
           this.$emit('apply-action')
+        },
+        confirmAccounts: () => {
+          const filtered = this.selectedUsers.filter(user => user.local && user.confirmation_pending)
+          const confirmAccountFn = async(users) => await this.$store.dispatch('ConfirmUsersEmail', users)
+
+          applyAction(filtered, confirmAccountFn)
+        },
+        resendConfirmation: () => {
+          const filtered = this.selectedUsers.filter(user => user.local && user.confirmation_pending)
+          const resendConfirmationFn = async(users) => await this.$store.dispatch('ResendConfirmationEmail', users)
+
+          applyAction(filtered, resendConfirmationFn)
         }
       }
     },
@@ -276,6 +297,20 @@ export default {
         removeTag(tag)
       )
     },
+    confirmAccountsForMultipleUsers() {
+      const { confirmAccounts } = this.mappers()
+      this.confirmMessage(
+        this.$t('users.confirmAccountsConfirmation'),
+        confirmAccounts
+      )
+    },
+    resendConfirmationForMultipleUsers() {
+      const { resendConfirmation } = this.mappers()
+      this.confirmMessage(
+        this.$t('users.resendEmailConfirmation'),
+        resendConfirmation
+      )
+    },
     confirmMessage(message, applyAction) {
       this.$confirm(message, {
         confirmButtonText: this.$t('users.ok'),
diff --git a/src/views/users/index.vue b/src/views/users/index.vue
index f9b5e10f..93b6a7f1 100644
--- a/src/views/users/index.vue
+++ b/src/views/users/index.vue
@@ -57,6 +57,11 @@
           <el-tag v-if="scope.row.roles.moderator">
             <span>{{ isDesktop ? $t('users.moderator') : getFirstLetter($t('users.moderator')) }}</span>
           </el-tag>
+          <el-tooltip :content="$t('users.unconfirmedEmail')" effect="dark">
+            <el-tag v-if="scope.row.confirmation_pending" type="info">
+              {{ isDesktop ? $t('users.unconfirmed') : getFirstLetter($t('users.unconfirmed')) }}
+            </el-tag>
+          </el-tooltip>
         </template>
       </el-table-column>
       <el-table-column :label="$t('users.actions')" fixed="right">
@@ -88,6 +93,17 @@
                 @click.native="handleDeletion(scope.row)">
                 {{ $t('users.deleteAccount') }}
               </el-dropdown-item>
+              <el-dropdown-item
+                v-if="scope.row.local && scope.row.confirmation_pending"
+                divided
+                @click.native="handleEmailConfirmation(scope.row)">
+                {{ $t('users.confirmAccount') }}
+              </el-dropdown-item>
+              <el-dropdown-item
+                v-if="scope.row.local && scope.row.confirmation_pending"
+                @click.native="handleConfirmationResend(scope.row)">
+                {{ $t('users.resendConfirmation') }}
+              </el-dropdown-item>
               <el-dropdown-item
                 :divided="showAdminAction(scope.row)"
                 :class="{ 'active-tag': scope.row.tags.includes('force_nsfw') }"
@@ -301,6 +317,12 @@ export default {
       user.roles[right]
         ? this.$store.dispatch('DeleteRight', { users: [user], right })
         : this.$store.dispatch('AddRight', { users: [user], right })
+    },
+    handleEmailConfirmation(user) {
+      this.$store.dispatch('ConfirmUsersEmail', [user])
+    },
+    handleConfirmationResend(user) {
+      this.$store.dispatch('ResendConfirmationEmail', [user])
     }
   }
 }
-- 
GitLab