diff --git a/src/api/users.js b/src/api/users.js index 8298eeca047c6b890bbe5f84061401adca901fca..642dd1a7d9bb888640f218d536f38cf74feb1f5b 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -71,6 +71,25 @@ export async function fetchUser(id, authHost, token) { }) } +export async function fetchUserCredentials(nickname, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/${nickname}/credentials`, + method: 'get', + headers: authHeaders(token) + }) +} + +export async function updateUserCredentials(nickname, credentials, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/${nickname}/credentials`, + method: 'patch', + headers: authHeaders(token), + data: credentials + }) +} + export async function fetchUsers(filters, authHost, token, page = 1) { return await request({ baseURL: baseName(authHost), diff --git a/src/lang/en.js b/src/lang/en.js index 8b8e9ff7a6ec7f740b78f1e06524a77058d9f677..5e98a3cbf164523675491d78d55b66185e2720c5 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -261,7 +261,22 @@ export default { activeUppercase: 'Active', active: 'active', deactivated: 'deactivated', - noStatuses: 'No statuses to show' + noStatuses: 'No statuses to show', + securitySettings: { + email: 'Email', + password: 'Password', + securitySettings: 'Security settings', + passwordChangeWarning1: 'Setting a new password will cause the user to be signed out from any client they have used before.', + passwordChangeWarning2: 'When the user signs in with this password, they will be asked to set a new one.', + passwordLengthNotice: 'Make sure it\'s at least {minLength} characters long.', + inputNewEmail: 'Input new email', + inputNewPassword: 'Input new password', + passwordUpdated: 'The password has been updated', + emailUpdated: 'The email has been updated', + success: 'Success', + submit: 'Submit', + close: 'Close' + } }, usersFilter: { inputPlaceholder: 'Select filter', diff --git a/src/store/modules/userProfile.js b/src/store/modules/userProfile.js index 5a7e4394cd84e6019256c5b3b63ae38ad556c510..af54072c5205783fcb16a37ea55274665a98ff21 100644 --- a/src/store/modules/userProfile.js +++ b/src/store/modules/userProfile.js @@ -1,10 +1,11 @@ -import { fetchUser, fetchUserStatuses } from '@/api/users' +import { fetchUser, fetchUserStatuses, fetchUserCredentials, updateUserCredentials } from '@/api/users' const userProfile = { state: { statuses: [], statusesLoading: true, user: {}, + userCredentials: {}, userProfileLoading: true }, mutations: { @@ -19,6 +20,9 @@ const userProfile = { }, SET_USER_PROFILE_LOADING: (state, status) => { state.userProfileLoading = status + }, + SET_USER_CREDENTIALS: (state, userCredentials) => { + state.userCredentials = userCredentials } }, actions: { @@ -38,6 +42,14 @@ const userProfile = { commit('SET_STATUSES', statuses.data) commit('SET_STATUSES_LOADING', false) + }, + async FetchUserCredentials({ commit, getters }, { nickname }) { + const userResponse = await fetchUserCredentials(nickname, getters.authHost, getters.token) + commit('SET_USER_CREDENTIALS', userResponse.data) + }, + async UpdateUserCredentials({ dispatch, getters }, { nickname, credentials }) { + await updateUserCredentials(nickname, credentials, getters.authHost, getters.token) + dispatch('FetchUserCredentials', { nickname }) } } } diff --git a/src/views/users/components/SecuritySettingsModal.vue b/src/views/users/components/SecuritySettingsModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..58c757b1bb24e82b5c9c3eacad327e6710da8218 --- /dev/null +++ b/src/views/users/components/SecuritySettingsModal.vue @@ -0,0 +1,155 @@ +<template> + <el-dialog + :before-close="close" + :title="$t('userProfile.securitySettings.securitySettings')" + :visible="visible" + class="security-settings-modal"> + <el-row> + <p> + <label> + {{ $t('userProfile.securitySettings.email') }} + </label> + </p> + </el-row> + <el-row> + <el-input + :placeholder="$t('userProfile.securitySettings.inputNewEmail')" + v-model="emailForm.newEmail" /> + </el-row> + <br> + <el-row type="flex" justify="end"> + <el-button + :loading="emailForm.isLoading" + :disabled="!emailForm.newEmail || emailForm.newEmail === userCredentials.email" + type="primary" + @click="updateEmail()"> + {{ $t('userProfile.securitySettings.submit') }} + </el-button> + </el-row> + <el-row> + <p> + <label> + {{ $t('userProfile.securitySettings.password') }} + </label> + </p> + </el-row> + <el-row> + <el-input + :placeholder="$t('userProfile.securitySettings.inputNewPassword')" + v-model="passwordForm.newPassword" + show-password /> + <small class="form-text"> + {{ $t('userProfile.securitySettings.passwordLengthNotice', { minLength: 8 }) }} + </small> + <br> + <el-alert + :closable="false" + type="warning" + show-icon> + <p>{{ $t('userProfile.securitySettings.passwordChangeWarning1') }}</p> + <p>{{ $t('userProfile.securitySettings.passwordChangeWarning2') }}</p> + </el-alert> + </el-row> + <br> + <el-row type="flex" justify="end"> + <el-button + :loading="passwordForm.isLoading" + :disabled="passwordForm.newPassword.length < 8" + type="primary" + @click="updatePassword()"> + {{ $t('userProfile.securitySettings.submit') }} + </el-button> + </el-row> + </el-dialog> +</template> + +<script> + +export default { + name: 'SecuritySettingsModal', + props: { + visible: { + type: Boolean, + default: false + }, + user: { + type: Object, + default: function() { + return {} + } + } + }, + data() { + return { + emailForm: { + newEmail: '', + isLoading: false + }, + passwordForm: { + newPassword: '', + isLoading: false + } + } + }, + computed: { + userCredentials() { + return this.$store.state.userProfile.userCredentials + } + }, + mounted: async function() { + await this.$store.dispatch('FetchUserCredentials', { nickname: this.user.nickname }) + this.emailForm.newEmail = this.userCredentials.email + }, + methods: { + async updateEmail() { + const credentials = { email: this.emailForm.newEmail } + this.emailForm.isLoading = true + await this.$store.dispatch('UpdateUserCredentials', { nickname: this.user.nickname, credentials }) + this.emailForm.isLoading = false + this.$notify.success({ + title: this.$t('userProfile.securitySettings.success'), + message: this.$t('userProfile.securitySettings.emailUpdated'), + duration: 2000 + }) + }, + async updatePassword() { + const credentials = { password: this.passwordForm.newPassword } + this.passwordForm.isLoading = true + await this.$store.dispatch('UpdateUserCredentials', { nickname: this.user.nickname, credentials }) + this.passwordForm.isLoading = false + this.passwordForm.newPassword = '' + this.$notify.success({ + title: this.$t('userProfile.securitySettings.success'), + message: this.$t('userProfile.securitySettings.passwordUpdated'), + duration: 2000 + }) + }, + close() { + this.$emit('close', true) + } + } +} +</script> + +<style rel='stylesheet/scss' lang='scss'> +@media all and (max-width: 800px) { + .security-settings-modal { + .el-dialog { + width: 90%; + } + } +} + +.security-settings-modal { + .el-alert .el-alert__description { + word-break: break-word; + font-size: 1em; + } + + .form-text { + display: block; + margin-top: .25rem; + color: #909399; + } +} +</style> diff --git a/src/views/users/show.vue b/src/views/users/show.vue index fd24486ddc1575bf11fbcde36ac6a6a28b6da3c8..84cfef323022fe30b37df302e70f3397f7b808ed 100644 --- a/src/views/users/show.vue +++ b/src/views/users/show.vue @@ -73,6 +73,18 @@ <el-tag v-if="user.deactivated" type="danger">{{ $t('userProfile.deactivated') }}</el-tag> </td> </tr> + <tr class="el-table__row"> + <td> + <el-button icon="el-icon-lock" @click="securitySettingsModalVisible = true"> + {{ $t('userProfile.securitySettings.securitySettings') }} + </el-button> + <SecuritySettingsModal + :user="user" + :visible="securitySettingsModalVisible" + @close="securitySettingsModalVisible = false" /> + </td> + <td /> + </tr> </tbody> </table> </div> @@ -96,14 +108,16 @@ <script> import Status from '@/components/Status' import ModerationDropdown from './components/ModerationDropdown' +import SecuritySettingsModal from './components/SecuritySettingsModal' export default { name: 'UsersShow', - components: { ModerationDropdown, Status }, + components: { ModerationDropdown, Status, SecuritySettingsModal }, data() { return { showPrivate: false, - resetPasswordDialogOpen: false + resetPasswordDialogOpen: false, + securitySettingsModalVisible: false } }, computed: { @@ -127,6 +141,9 @@ export default { }, userProfileLoading() { return this.$store.state.userProfile.userProfileLoading + }, + userCredentials() { + return this.$store.state.userProfile.userCredentials } }, mounted: function() {