mirror of
https://github.com/Bubka/2FAuth.git
synced 2024-12-04 22:31:58 +01:00
325 lines
14 KiB
Vue
325 lines
14 KiB
Vue
<script setup>
|
|
import CopyButton from '@/components/CopyButton.vue'
|
|
import AccessLogViewer from '@/components/AccessLogViewer.vue'
|
|
import userService from '@/services/userService'
|
|
import { useNotifyStore } from '@/stores/notify'
|
|
import { UseColorMode } from '@vueuse/components'
|
|
import { useUserStore } from '@/stores/user'
|
|
import { useBusStore } from '@/stores/bus'
|
|
|
|
const notify = useNotifyStore()
|
|
const router = useRouter()
|
|
const user = useUserStore()
|
|
const bus = useBusStore()
|
|
|
|
const isFetching = ref(false)
|
|
const managedUser = ref(null)
|
|
const listUserPreferences = ref(null)
|
|
const showFullLogLink = ref(false)
|
|
|
|
const props = defineProps({
|
|
userId: [Number, String]
|
|
})
|
|
|
|
onMounted(async () => {
|
|
await getUser()
|
|
})
|
|
|
|
/**
|
|
* Gets the user from backend
|
|
*/
|
|
async function getUser() {
|
|
isFetching.value = true
|
|
|
|
userService.getById(props.userId, {returnError: true})
|
|
.then(response => {
|
|
managedUser.value = response.data
|
|
bus.username = managedUser.value.info.name
|
|
})
|
|
.catch(error => {
|
|
notify.error(error)
|
|
})
|
|
.finally(() => {
|
|
isFetching.value = false
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Resends a pwd reset email to the user
|
|
*/
|
|
async function resendPasswordEmail() {
|
|
if (! confirmForYourself()) {
|
|
return false
|
|
}
|
|
|
|
if (confirm(trans('admin.confirm.purge_password_reset_request')) === true) {
|
|
await userService.resendPasswordEmail(managedUser.value.info.id)
|
|
managedUser.value.password_reset = null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the user password
|
|
*/
|
|
async function resetPassword() {
|
|
if (! confirmForYourself()) {
|
|
return false
|
|
}
|
|
|
|
if (confirm(trans('admin.confirm.request_password_reset')) === true) {
|
|
userService.resetPassword(managedUser.value.info.id, { returnError: true })
|
|
.then(response => {
|
|
managedUser.value = response.data
|
|
notify.success({ text: trans('admin.password_successfully_reset') })
|
|
})
|
|
.catch(error => {
|
|
if(error.response.status === 400) {
|
|
notify.alert({ text: error.response.data.reason })
|
|
}
|
|
else notify.error(error)
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set admin role
|
|
*
|
|
* @param {string} preference
|
|
* @param {boolean} isAdmin
|
|
*/
|
|
function saveAdminRole(isAdmin) {
|
|
if (! confirm(trans('admin.confirm.change_admin_role'))) {
|
|
nextTick().then(() => {
|
|
managedUser.value.info.is_admin = ! isAdmin
|
|
})
|
|
return
|
|
}
|
|
|
|
if(isAdmin === false && managedUser.value.info.id === user.id) {
|
|
if (! confirm(trans('admin.confirm.demote_own_account'))) {
|
|
nextTick().then(() => {
|
|
managedUser.value.info.is_admin = true
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
userService.promote(managedUser.value.info.id, { 'is_admin': isAdmin }, { returnError: true }).then(response => {
|
|
managedUser.value.info.is_admin = response.data.info.is_admin
|
|
notify.success({ text: trans('admin.user_role_updated') })
|
|
})
|
|
.catch(error => {
|
|
if( error.response.status === 403 ) {
|
|
notify.alert({ text: error.response.data.message })
|
|
managedUser.value.info.is_admin = true
|
|
}
|
|
else {
|
|
notify.error(error.response)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* submit user account deletion
|
|
*/
|
|
function deleteUser() {
|
|
if (! confirmForYourself()) {
|
|
return false
|
|
}
|
|
|
|
if(confirm(trans('admin.confirm.delete_account'))) {
|
|
userService.delete(managedUser.value.info.id, { returnError: true }).then(response => {
|
|
notify.success({ text: trans('auth.forms.user_account_successfully_deleted') })
|
|
router.push({ name: 'admin.users' });
|
|
})
|
|
.catch(error => {
|
|
if( error.response.status === 403 ) {
|
|
notify.alert({ text: error.response.data.message })
|
|
}
|
|
else {
|
|
notify.error(error.response)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* submit user account deletion
|
|
*/
|
|
function revokePATs() {
|
|
if (! confirmForYourself()) {
|
|
return false
|
|
}
|
|
|
|
userService.revokePATs(managedUser.value.info.id).then(response => {
|
|
managedUser.value.valid_personal_access_tokens = 0
|
|
notify.success({ text: trans('admin.pats_succesfully_revoked') })
|
|
})
|
|
}
|
|
|
|
/**
|
|
* submit user account deletion
|
|
*/
|
|
function revokeWebauthnCredentials() {
|
|
if (! confirmForYourself()) {
|
|
return false
|
|
}
|
|
|
|
userService.revokeWebauthnCredentials(managedUser.value.info.id).then(response => {
|
|
managedUser.value.valid_personal_access_tokens = 0
|
|
notify.success({ text: trans('admin.security_devices_succesfully_revoked') })
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Confirmation for modification on own account
|
|
*/
|
|
function confirmForYourself() {
|
|
if(managedUser.value.info.id === user.id) {
|
|
if (! confirm(trans('admin.confirm.edit_own_account'))) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
<ResponsiveWidthWrapper>
|
|
<h1 class="title has-text-grey-dark mb-6">
|
|
{{ $t('admin.user_management') }}
|
|
</h1>
|
|
<!-- loader -->
|
|
<div v-if="isFetching || ! managedUser" class="has-text-centered">
|
|
<span class="is-size-4">
|
|
<FontAwesomeIcon :icon="['fas', 'spinner']" spin />
|
|
</span>
|
|
</div>
|
|
<div v-else>
|
|
<div class="mb-6" :class="managedUser.info.is_admin ? 'is-left-bordered-warning' : 'is-left-bordered-link'">
|
|
<UseColorMode v-slot="{ mode }">
|
|
<p class="title is-4" :class="{ 'has-text-grey-lighter' : mode == 'dark' }">
|
|
<span class="has-text-weight-light has-text-grey-dark is-pulled-right">#{{ managedUser.info.id }}</span>{{ managedUser.info.name }}</p>
|
|
</UseColorMode>
|
|
<p class="subtitle is-6 block">{{ managedUser.info.email }}</p>
|
|
</div>
|
|
<!-- oauth banner -->
|
|
<div v-if="managedUser.info.oauth_provider" class="notification is-dark is-size-7-mobile has-text-centered">
|
|
{{ $t('admin.account_bound_to_x_via_oauth', { provider: managedUser.info.oauth_provider }) }}
|
|
</div>
|
|
<div class="block is-size-6 is-size-7-mobile has-text-grey">
|
|
{{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
|
|
</div>
|
|
<div class="block">
|
|
<!-- otp as dot -->
|
|
<FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" />
|
|
</div>
|
|
<h2 class="title is-4 has-text-grey-light">{{ $t('admin.access') }}</h2>
|
|
<div class="block">
|
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey">
|
|
<div class="mb-3 is-flex is-justify-content-space-between">
|
|
<div>
|
|
<span class="has-text-weight-bold">{{ $t('auth.forms.password') }}</span>
|
|
</div>
|
|
<div>
|
|
<div class="tags ml-3 is-right">
|
|
<UseColorMode v-slot="{ mode }">
|
|
<!-- resend email button -->
|
|
<button v-if="managedUser.password_reset" class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resendPasswordEmail" :title="$t('admin.resend_email_title')">
|
|
{{ $t('admin.resend_email') }}
|
|
</button>
|
|
<!-- reset password button -->
|
|
<button class="button tag is-pulled-right " :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="resetPassword" :title="$t('admin.reset_password_title')">
|
|
{{ $t('admin.reset_password') }}
|
|
</button>
|
|
</UseColorMode>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="is-size-7 is-size-7-mobile has-text-grey-dark">
|
|
<span v-if="managedUser.password_reset === 0" v-html="$t('admin.password_request_expired')" class="is-block block"></span>
|
|
<span v-else-if="managedUser.password_reset" v-html="$t('admin.password_requested_on_t', { datetime: managedUser.password_reset })" class="is-block block"></span>
|
|
<span v-if="managedUser.password_reset" v-html="$t('admin.resend_email_help')" class="is-block block"></span>
|
|
<span v-html="$t('admin.reset_password_help')" class="is-block block"></span>
|
|
</div>
|
|
</div>
|
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
|
<div>
|
|
<span class="has-text-weight-bold">{{ $t('settings.personal_access_tokens') }}</span>
|
|
<span class="is-block is-family-primary has-text-grey-dark">
|
|
{{ $t('admin.user_has_x_active_pat', { count: managedUser.valid_personal_access_tokens }) }}
|
|
</span>
|
|
</div>
|
|
<div v-if="managedUser.valid_personal_access_tokens > 0">
|
|
<div class="tags ml-3 is-right">
|
|
<UseColorMode v-slot="{ mode }">
|
|
<!-- manage link -->
|
|
<button class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" @click="revokePATs" :title="$t('admin.revoke_all_pat_for_user')">
|
|
{{ $t('settings.revoke') }}
|
|
</button>
|
|
</UseColorMode>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
|
|
<div>
|
|
<span class="has-text-weight-bold">{{ $t('auth.webauthn.security_devices') }}</span>
|
|
<span class="is-block has-text-grey-dark">
|
|
{{ $t('admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
|
|
</span>
|
|
</div>
|
|
<div v-if="managedUser.webauthn_credentials > 0">
|
|
<div class="tags ml-3 is-right">
|
|
<UseColorMode v-slot="{ mode }">
|
|
<!-- manage link -->
|
|
<button class="button tag is-pulled-right" :class="mode == 'dark' ? 'is-dark has-background-link' : 'is-white'" :title="$t('admin.revoke_all_devices_for_user')">
|
|
{{ $t('settings.revoke') }}
|
|
</button>
|
|
</UseColorMode>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="block">
|
|
<h3 class="title is-5 has-text-grey-light mb-2">{{ $t('admin.last_accesses') }}</h3>
|
|
<AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/>
|
|
</div>
|
|
<div v-if="showFullLogLink" class="block is-size-6 is-size-7-mobile has-text-grey">
|
|
{{ $t('admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
|
|
{{ $t('admin.see_full_log') }}.
|
|
</router-link>
|
|
</div>
|
|
<!-- preferences -->
|
|
<h2 class="title is-4 has-text-grey-light">{{ $t('settings.preferences') }}</h2>
|
|
<div class="about-debug box is-family-monospace is-size-7">
|
|
<CopyButton id="btnCopyEnvVars" :token="listUserPreferences?.innerText" />
|
|
<ul ref="listUserPreferences" id="listUserPreferences">
|
|
<li v-for="(value, preference) in managedUser.info.preferences" :value="value" :key="preference">
|
|
<b>{{preference}}</b>: <span class="has-text-grey">{{value}}</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<!-- danger zone -->
|
|
<h2 class="title is-4 has-text-danger">{{ $t('admin.danger_zone') }}</h2>
|
|
<div class="is-left-bordered-danger">
|
|
<div class="block is-size-6 is-size-7-mobile">
|
|
{{ $t('admin.delete_this_user_legend') }}
|
|
<span class="is-block has-text-grey has-text-weight-bold">
|
|
{{ $t('admin.this_is_not_soft_delete') }}
|
|
</span>
|
|
</div>
|
|
<button class="button is-danger" @click="deleteUser" title="delete">
|
|
{{ $t('admin.delete_this_user') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- footer -->
|
|
<VueFooter :showButtons="true">
|
|
<ButtonBackCloseCancel :returnTo="{ name: 'admin.users' }" action="back" />
|
|
<ButtonBackCloseCancel :returnTo="{ name: 'accounts' }" action="close" />
|
|
</VueFooter>
|
|
</ResponsiveWidthWrapper>
|
|
</template>
|