mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #3274 from modoboa/fix/beta2_review
Fixes after review of beta2
This commit is contained in:
+1
-1
@@ -269,7 +269,7 @@ by default.
|
||||
'OIDC_ENABLED': True,
|
||||
'OIDC_RP_INITIATED_LOGOUT_ENABLED': True,
|
||||
'OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT': True,
|
||||
'OIDC_RSA_PRIVATE_KEY': env('OIDC_RSA_PRIVATE_KEY'),
|
||||
'OIDC_RSA_PRIVATE_KEY': env.str('OIDC_RSA_PRIVATE_KEY', multiline=True),
|
||||
'SCOPES': {
|
||||
'openid': 'OpenID Connect scope',
|
||||
'read': 'Read scope',
|
||||
|
||||
@@ -151,9 +151,9 @@ function fetchAlarms({ page, itemsPerPage, sortBy }) {
|
||||
page: page || 1,
|
||||
page_size: itemsPerPage || 10,
|
||||
}
|
||||
if (sortBy) {
|
||||
if (sortBy && sortBy.length) {
|
||||
params.ordering = sortBy
|
||||
.map((item) => (this.options.sortDesc[0] ? `-${item}` : item))
|
||||
.map((item) => (item.order !== 'asc' ? `-${item.key}` : item.key))
|
||||
.join(',')
|
||||
}
|
||||
if (search.value !== '') {
|
||||
@@ -177,7 +177,7 @@ async function deleteAlarm(alarm, single = true) {
|
||||
}
|
||||
async function deleteAlarms() {
|
||||
await alarmsApi.bulkDelete(selected.value.map((alarm) => alarm.id))
|
||||
busStore.displayNotification({ msg: this.$gettext('Alarms deleted') })
|
||||
busStore.displayNotification({ msg: $gettext('Alarms deleted') })
|
||||
selected.value = []
|
||||
fetchAlarms({})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div v-if="alignments">
|
||||
<v-card>
|
||||
<v-toolbar dense elevation="0" class="mr-2">
|
||||
<v-toolbar-title>$gettext('Alignment')</v-toolbar-title>
|
||||
<v-toolbar-title>{{ $gettext('Alignment') }}</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn icon="mdi-arrow-left" size="x-small" @click="previousWeek">
|
||||
</v-btn>
|
||||
@@ -153,8 +153,8 @@ const boxes = ref([
|
||||
])
|
||||
|
||||
const now = DateTime.now()
|
||||
const currentWeek = ref(now.year)
|
||||
const currentYear = ref(now.weekNumber)
|
||||
const currentYear = ref(now.year)
|
||||
const currentWeek = ref(now.weekNumber)
|
||||
const dmarcDisabled = ref(false)
|
||||
const loading = ref(false)
|
||||
const panel = ref(null)
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<v-container class="fill-height" fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
class="d-flex flex-column justify-center pa-10"
|
||||
@keyup.enter="changePassword"
|
||||
>
|
||||
<h4 class="text-primary mb-6 text-h4">
|
||||
{{ $gettext('Forgot password?') }}
|
||||
</h4>
|
||||
<v-form ref="observer">
|
||||
<p id="password_validation">
|
||||
{{ password_validation_error }}
|
||||
</p>
|
||||
<label class="m-label">{{ $gettext('Change password') }}</label>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="password_confirmed"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
:rules="[
|
||||
rules.required,
|
||||
(value) => rules.samePassword(value, password),
|
||||
]"
|
||||
/>
|
||||
</v-form>
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
class="flex-grow-1"
|
||||
color="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="changePassword"
|
||||
>
|
||||
{{ $gettext('Submit') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<div>
|
||||
<router-link :to="{ name: 'Login' }" class="float-right text-primary">
|
||||
{{ $gettext('Return to login') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import auth from '@/api/auth'
|
||||
import rules from '@/plugins/rules.js'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const loading = ref(false)
|
||||
const password = ref('')
|
||||
const password_confirmed = ref('')
|
||||
const password_validation_error = ref('')
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const observer = ref(null)
|
||||
|
||||
let token
|
||||
let id
|
||||
|
||||
function checkUrl() {
|
||||
if (route.params.token === undefined) {
|
||||
if (token === '' || id === '') {
|
||||
router.push({ name: 'PasswordRecovery' })
|
||||
}
|
||||
} else {
|
||||
const decodedId = atob(route.params.id)
|
||||
if (!/^\d+$/.test(decodedId)) {
|
||||
router.push({ name: 'PasswordRecovery' })
|
||||
} else {
|
||||
id = route.params.id
|
||||
token = route.params.token
|
||||
router.push({ name: 'PasswordRecoveryChangeForm' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function changePassword() {
|
||||
const { valid } = await observer.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
new_password1: password.value,
|
||||
new_password2: password_confirmed.value,
|
||||
token: token,
|
||||
id: id,
|
||||
}
|
||||
loading.value = true
|
||||
password_validation_error.value = ''
|
||||
auth
|
||||
.changePassword(payload)
|
||||
.then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
loading.value = false
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
let message = ''
|
||||
loading.value = false
|
||||
if (err.response.status === 403) {
|
||||
message = $gettext('Invalid reset token.')
|
||||
} else if (err.response.status === 404) {
|
||||
message = $gettext('User unknown.')
|
||||
} else if (
|
||||
err.response.status === 400 &&
|
||||
err.response.data.type === 'password_requirement'
|
||||
) {
|
||||
err.response.data.errors.forEach((element) => {
|
||||
message += $gettext(element) + '<br>'
|
||||
})
|
||||
} else if (err.response.status === 429) {
|
||||
message = $gettext('Too many unsuccessful attempts, please try later.')
|
||||
}
|
||||
password_validation_error.value = message
|
||||
})
|
||||
}
|
||||
|
||||
checkUrl()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#password_validation {
|
||||
color: crimson;
|
||||
}
|
||||
</style>
|
||||
@@ -1,130 +0,0 @@
|
||||
<template>
|
||||
<v-container class="fill-height" fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="10"
|
||||
md="8"
|
||||
class="text-center"
|
||||
@keyup.enter="recoverPassword"
|
||||
>
|
||||
<h4 class="text-primary mb-10 text-h4">
|
||||
{{ $gettext('Forgot password?') }}
|
||||
</h4>
|
||||
<v-form ref="observer" class="my-4">
|
||||
<label class="m-label">{{
|
||||
$gettext('Please fill-in your primary email address')
|
||||
}}</label>
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
variant="outlined"
|
||||
:rules="[rules.required, rules.email]"
|
||||
:error-messages="errors"
|
||||
/>
|
||||
</v-form>
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
class="flex-grow-1"
|
||||
color="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="recoverPassword"
|
||||
>
|
||||
{{ $gettext('Submit') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<div>
|
||||
<a class="float-right text-primary" @click="returnToLogin">{{
|
||||
$gettext('Return to login')
|
||||
}}</a>
|
||||
</div>
|
||||
</v-col>
|
||||
<ConfirmDialog ref="dialog" />
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
|
||||
import auth from '@/api/auth'
|
||||
import rules from '@/plugins/rules.js'
|
||||
|
||||
const router = useRouter()
|
||||
const { $gettext } = useGettext()
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
const dialog = ref(null)
|
||||
const observer = ref(null)
|
||||
const errors = ref([])
|
||||
|
||||
function returnToLogin() {
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
|
||||
async function showDialog(title, body, error = false) {
|
||||
let color = 'info'
|
||||
if (error) {
|
||||
color = 'error'
|
||||
}
|
||||
const confirm = await dialog.value.open(title, body, {
|
||||
color: color,
|
||||
cancelLabel: $gettext('Ok'),
|
||||
agreeLabel: $gettext('Return to login'),
|
||||
})
|
||||
if (!confirm) {
|
||||
return
|
||||
}
|
||||
returnToLogin()
|
||||
}
|
||||
|
||||
async function recoverPassword() {
|
||||
const { valid } = await observer.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
email: email.value,
|
||||
}
|
||||
loading.value = true
|
||||
auth
|
||||
.recoverPassword(payload)
|
||||
.then(async (resp) => {
|
||||
loading.value = false
|
||||
if (resp.status === 200) {
|
||||
if (resp.data.type === 'sms') {
|
||||
router.push({ name: 'PasswordRecoverySmsTotpForm' })
|
||||
} else if (resp.data.type === 'email') {
|
||||
await showDialog(
|
||||
'Info',
|
||||
$gettext('Email sent, please check your inbox')
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
loading.value = false
|
||||
if (err.response.status === 404) {
|
||||
if (
|
||||
err.response.data.type === 'sms' ||
|
||||
err.response.data.type === 'email'
|
||||
) {
|
||||
errors.value = [$gettext(err.response.data.reason)]
|
||||
}
|
||||
} else if (
|
||||
err.response.status === 503 &&
|
||||
err.response.data.type === 'email'
|
||||
) {
|
||||
showDialog('Error', err.response.data.reason, true)
|
||||
} else if (err.response.status === 429) {
|
||||
errors.value = [
|
||||
$gettext('Too many unsuccessful attempts, please try later.'),
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<v-container class="fill-height" fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
class="d-flex flex-column justify-center pa-10"
|
||||
@keyup.enter="changePassword"
|
||||
>
|
||||
<ConfirmDialog ref="confirm" />
|
||||
<h4 class="text-primary mb-6 text-h4">
|
||||
{{ $gettext('Password recovery') }}
|
||||
</h4>
|
||||
<v-form ref="observer">
|
||||
<label class="m-label"
|
||||
>{{ $gettext("Enter the code you've just received by SMS") }}
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="sms_totp"
|
||||
type="totp"
|
||||
variant="outlined"
|
||||
:rules="[rules.required, (value) => rules.length(value, 6)]"
|
||||
:error-messages="errors"
|
||||
/>
|
||||
</v-form>
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
class="flex-grow-1"
|
||||
color="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="checkSmsTotp"
|
||||
>
|
||||
{{ $gettext('Submit') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<div>
|
||||
<a class="float-left text-primary" @click="resendSms">{{
|
||||
$gettext('Resend SMS.')
|
||||
}}</a>
|
||||
<router-link
|
||||
:to="{ name: 'Login' }"
|
||||
class="float-right text-primary"
|
||||
>{{ $gettext('Return to login') }}</router-link
|
||||
>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref } from 'vue'
|
||||
import auth from '@/api/auth'
|
||||
import rules from '@/plugins/rules.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const router = useRouter()
|
||||
const confirm = ref(null)
|
||||
const loading = ref(false)
|
||||
const observer = ref(null)
|
||||
const sms_totp = ref('')
|
||||
const errors = ref([])
|
||||
|
||||
async function showErrorDialog(body) {
|
||||
const confirm = await confirm.value.open($gettext('Error'), body, {
|
||||
color: 'error',
|
||||
cancelLabel: $gettext('Return to login'),
|
||||
agreeLabel: $gettext('Retry reset'),
|
||||
})
|
||||
if (!confirm) {
|
||||
router.push({ name: 'Login' })
|
||||
} else {
|
||||
router.push({ name: 'PasswordRecoveryForm' })
|
||||
}
|
||||
}
|
||||
|
||||
async function resendSms() {
|
||||
auth
|
||||
.checkSmsTotp({ type: 'resend' })
|
||||
.then((resp) => {
|
||||
loading.value = false
|
||||
if (resp.status === 200) {
|
||||
observer.value.setErrors({
|
||||
sms_totp: this.$gettext('TOTP resent.'),
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
if (err.response.status === 400) {
|
||||
this.loading = false
|
||||
await showErrorDialog(
|
||||
$gettext(
|
||||
'User seems wrong, return to login or restart reset the process?'
|
||||
)
|
||||
)
|
||||
} else if (err.response.status === 429) {
|
||||
errors.value = [
|
||||
$gettext('Too many unsuccessful attempts, please try later.'),
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function checkSmsTotp() {
|
||||
const { valid } = await observer.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
type: 'confirm',
|
||||
sms_totp: this.sms_totp,
|
||||
}
|
||||
loading.value = true
|
||||
auth
|
||||
.checkSmsTotp(payload)
|
||||
.then((resp) => {
|
||||
loading.value = false
|
||||
if (resp.status === 200) {
|
||||
// Code is good, redirecting to change password form.
|
||||
router.push({
|
||||
name: 'PasswordRecoveryChangeForm',
|
||||
data: resp.response.data,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
loading.value = false
|
||||
if (err.response.status === 403 && 'reason' in err.response.data) {
|
||||
await showErrorDialog(err.response.data.reason)
|
||||
} else if (err.response.status === 400 && 'reason' in err.response.data) {
|
||||
errors.value = [$gettext(err.response.data.reason)]
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<v-container class="fill-height" fuild>
|
||||
<v-row align="center" justify="center" dense>
|
||||
<v-col cols="12" sm="10" md="8" lg="8" class="text-center">
|
||||
<h4 class="text-primary mb-10 text-h4">
|
||||
{{ $gettext('Two-factor authentication') }}
|
||||
</h4>
|
||||
<v-form ref="loginForm" @submit.prevent="verifyCode">
|
||||
<div class="text-body-2">
|
||||
{{
|
||||
$gettext(
|
||||
'Enter the code from the two-factor app on your mobile device. If you have lost your device, you may enter one of your recovery codes.'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<v-otp-input
|
||||
ref="otpInput"
|
||||
v-model="code"
|
||||
length="6"
|
||||
:rules="[rules.required]"
|
||||
@finish="verifyCode"
|
||||
/>
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
size="large"
|
||||
type="submit"
|
||||
:loading="loading"
|
||||
class="mb-5"
|
||||
>
|
||||
{{ $gettext('Verify code') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import account from '@/api/account'
|
||||
import Cookies from 'js-cookie'
|
||||
import rules from '@/plugins/rules.js'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const loading = ref(false)
|
||||
const code = ref('')
|
||||
const loginForm = ref()
|
||||
|
||||
async function verifyCode() {
|
||||
const { valid } = await loginForm.value.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
account
|
||||
.verifyTFACode(code.value)
|
||||
.then((resp) => {
|
||||
Cookies.set('token', resp.data.access, { sameSite: 'strict' })
|
||||
Cookies.set('refreshToken', resp.data.refresh, {
|
||||
sameSite: 'strict',
|
||||
})
|
||||
authStore.initialize().then(() => {
|
||||
// bus.$emit('loggedIn')
|
||||
router.push({ name: 'DomainList' })
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -42,3 +42,9 @@ busStore.$onAction(({ name, after }) => {
|
||||
}
|
||||
}, true)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.v-application {
|
||||
background-color: #f7f8fa;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,14 +46,15 @@
|
||||
>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item
|
||||
v-for="subitem in item.children"
|
||||
:key="subitem.text"
|
||||
:to="subitem.to"
|
||||
link
|
||||
:title="subitem.text"
|
||||
:value="subitem"
|
||||
></v-list-item>
|
||||
<template v-for="subitem in item.children" :key="subitem.text">
|
||||
<v-list-item
|
||||
v-if="displayMenuItem(subitem)"
|
||||
:to="subitem.to"
|
||||
link
|
||||
:title="subitem.text"
|
||||
:value="subitem"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-list-group>
|
||||
</template>
|
||||
</template>
|
||||
@@ -176,7 +177,7 @@ const mainMenuItems = [
|
||||
text: $gettext('Dashboard'),
|
||||
to: { name: 'Dashboard' },
|
||||
exact: true,
|
||||
icon: 'mdi-view-dashboard-outline'
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
},
|
||||
{
|
||||
text: $gettext('Domains'),
|
||||
@@ -245,7 +246,7 @@ const mainMenuItems = [
|
||||
text: $gettext('Information'),
|
||||
roles: ['SuperAdmins'],
|
||||
to: { name: 'Information' },
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const userMenuItems = [
|
||||
@@ -262,11 +263,11 @@ const userMenuItems = [
|
||||
},
|
||||
]
|
||||
|
||||
if (isAuthenticated && authUser.value.role !== 'SimpleUsers') {
|
||||
if (isAuthenticated.value && authUser.value.role !== 'SimpleUsers') {
|
||||
userMenuItems.unshift({
|
||||
text: $gettext('Admin'),
|
||||
to: { name: 'Dashboard' },
|
||||
icon: 'mdi-view-dashboard-outline'
|
||||
icon: 'mdi-view-dashboard-outline',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -277,13 +278,13 @@ function getUserSettingsMenuItems() {
|
||||
result.push({
|
||||
text: $gettext('Filters'),
|
||||
to: { name: 'AccountFilters' },
|
||||
icon: 'mdi-filter'
|
||||
icon: 'mdi-filter',
|
||||
})
|
||||
}
|
||||
result.push({
|
||||
text: $gettext('Settings'),
|
||||
to: { name: 'AccountSettings' },
|
||||
icon: 'mdi-cog'
|
||||
icon: 'mdi-cog',
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -318,7 +319,10 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
}
|
||||
if (authUser.value.role === 'SuperAdmins' && parametersStore.imapMigrationEnabled === null) {
|
||||
if (
|
||||
authUser.value.role === 'SuperAdmins' &&
|
||||
parametersStore.imapMigrationEnabled === null
|
||||
) {
|
||||
parametersApi.getApplication('imap_migration').then((response) => {
|
||||
parametersStore.imapMigrationEnabled =
|
||||
response.data.params.enabled_imapmigration
|
||||
|
||||
@@ -12,33 +12,11 @@ const routes = [
|
||||
name: 'Login',
|
||||
component: () => import('@/views/login/LoginView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'twofa',
|
||||
name: 'TwoFA',
|
||||
component: () => import('@/views/login/TwoFA.vue'),
|
||||
},
|
||||
{
|
||||
path: 'logged',
|
||||
name: 'LoginCallback',
|
||||
component: () => import('@/views/login/LoginCallbackView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'password_recovery',
|
||||
name: 'PasswordRecovery',
|
||||
component: () => import('../views/login/PasswordRecoveryView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'password_recovery/confirm/:id?/:token?/',
|
||||
name: 'PasswordRecoveryChangeForm',
|
||||
component: () =>
|
||||
import('../views/login/PasswordRecoveryChangeView.vue'),
|
||||
},
|
||||
{
|
||||
path: 'password_recovery/sms_confirm',
|
||||
name: 'PasswordRecoverySms',
|
||||
component: () =>
|
||||
import('../views/login/PasswordRecoverySmsTotpView.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -65,3 +65,9 @@ const tab = ref()
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.v-window__container {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,59 +19,61 @@
|
||||
v-for="element in displayableElements"
|
||||
:key="element.label"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<template
|
||||
v-for="param in displayableParams(element.parameters)"
|
||||
:key="param.name"
|
||||
>
|
||||
<div class="my-4">
|
||||
<template v-if="param.widget === 'SeparatorField'">
|
||||
<h2>{{ param.label }}</h2>
|
||||
</template>
|
||||
<template v-else-if="param.widget === 'BooleanField'">
|
||||
<v-switch
|
||||
v-if="param.widget === 'BooleanField'"
|
||||
v-model="parameters[param.name]"
|
||||
:label="param.label"
|
||||
:hint="param.help_text"
|
||||
:error="formErrors[param.name] !== undefined"
|
||||
color="primary"
|
||||
density="compact"
|
||||
:error-messages="formErrors[param.name]"
|
||||
persistent-hint
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label class="m-label">{{ param.label }}</label>
|
||||
<v-select
|
||||
v-if="param.widget === 'ChoiceField'"
|
||||
v-model="parameters[param.name]"
|
||||
:items="param.choices"
|
||||
item-title="text"
|
||||
:hint="param.help_text"
|
||||
persistent-hint
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="parameters[param.name]"
|
||||
:hint="param.help_text"
|
||||
persistent-hint
|
||||
:error="formErrors[param.name] !== undefined"
|
||||
:error-messages="formErrors[param.name]"
|
||||
:type="
|
||||
param.widget === 'PasswordField' ? 'password' : 'text'
|
||||
"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-container fluid class="pa-0">
|
||||
<v-card>
|
||||
<v-card-text>
|
||||
<template
|
||||
v-for="param in displayableParams(element.parameters)"
|
||||
:key="param.name"
|
||||
>
|
||||
<div class="my-4">
|
||||
<template v-if="param.widget === 'SeparatorField'">
|
||||
<h2>{{ param.label }}</h2>
|
||||
</template>
|
||||
<template v-else-if="param.widget === 'BooleanField'">
|
||||
<v-switch
|
||||
v-if="param.widget === 'BooleanField'"
|
||||
v-model="parameters[param.name]"
|
||||
:label="param.label"
|
||||
:hint="param.help_text"
|
||||
:error="formErrors[param.name] !== undefined"
|
||||
color="primary"
|
||||
density="compact"
|
||||
:error-messages="formErrors[param.name]"
|
||||
persistent-hint
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<label class="m-label">{{ param.label }}</label>
|
||||
<v-select
|
||||
v-if="param.widget === 'ChoiceField'"
|
||||
v-model="parameters[param.name]"
|
||||
:items="param.choices"
|
||||
item-title="text"
|
||||
:hint="param.help_text"
|
||||
persistent-hint
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="parameters[param.name]"
|
||||
:hint="param.help_text"
|
||||
persistent-hint
|
||||
:error="formErrors[param.name] !== undefined"
|
||||
:error-messages="formErrors[param.name]"
|
||||
:type="
|
||||
param.widget === 'PasswordField' ? 'password' : 'text'
|
||||
"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
<v-btn
|
||||
@@ -193,3 +195,9 @@ loadParams(route.params.app)
|
||||
color: #ff5252 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.v-window__container {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="text-h4">{{ $gettext('Edit domain') }}</span>
|
||||
<div class="mt-4" />
|
||||
<DomainEditForm :domain="domain" />
|
||||
</div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Edit domain') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<DomainEditForm :domain="domain" />
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
@@ -23,3 +22,9 @@ domainsStore
|
||||
.getDomain(route.params.id)
|
||||
.then((response) => (domain.value = response.data))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
{{ $gettext('DMARC') }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-tabs-window v-model="tab">
|
||||
<v-tabs-window-item>
|
||||
<v-row>
|
||||
@@ -117,3 +118,9 @@ function refreshDomain() {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.v-window__container {
|
||||
width: 100% !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<span class="text-h4">{{ $gettext('Edit account') }}</span>
|
||||
<div class="mt-4" />
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Edit account') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<AccountEditForm />
|
||||
</template>
|
||||
|
||||
@@ -10,3 +11,9 @@ import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<span class="text-h4">{{ $gettext('Edit alias') }}</span>
|
||||
<div class="mt-4" />
|
||||
<AliasEditForm :alias="alias" />
|
||||
</div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Edit alias') }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<AliasEditForm :alias="alias" />
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
@@ -12,3 +11,9 @@ import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<template v-if="smAndDown">
|
||||
<PasswordRecoveryChangeForm />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row class="fill-height ma-0">
|
||||
<v-col class="bg-primary">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="300"
|
||||
class="mt-10 ml-8"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
<PasswordRecoveryChangeForm />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import PasswordRecoveryChangeForm from '@/components/login/PasswordRecoveryChangeForm.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { smAndDown } = useDisplay()
|
||||
</script>
|
||||
@@ -1,129 +0,0 @@
|
||||
<template>
|
||||
<v-row style="height: 100%; margin: 0 auto">
|
||||
<v-col cols="0" sm="6" class="primary" style="height: 100%">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="300"
|
||||
class="mt-10 ml-8"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
class="d-flex flex-column justify-center pa-10"
|
||||
@keyup.enter="recoverPassword"
|
||||
>
|
||||
<span class="text-primary mb-6 text-h4">{{
|
||||
$gettext('Forgot password?')
|
||||
}}</span>
|
||||
<v-form ref="observer">
|
||||
<label class="m-label">{{
|
||||
$gettext('Please fill-in your primary email address')
|
||||
}}</label>
|
||||
<v-text-field
|
||||
v-model="email"
|
||||
variant="outlined"
|
||||
:rules="[rules.required, rules.email]"
|
||||
:error-messages="errors"
|
||||
/>
|
||||
</v-form>
|
||||
<div class="d-flex justify-center">
|
||||
<v-btn
|
||||
class="flex-grow-1"
|
||||
color="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="recoverPassword"
|
||||
>
|
||||
{{ $gettext('Submit') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<div>
|
||||
<a :to="{ name: 'Login' }" class="float-right text-primary">{{
|
||||
$gettext('Return to login')
|
||||
}}</a>
|
||||
</div>
|
||||
</v-col>
|
||||
<ConfirmDialog ref="confirm" />
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
|
||||
import auth from '@/api/auth'
|
||||
import rules from '@/plugins/rules.js'
|
||||
|
||||
const router = useRouter()
|
||||
const { $gettext } = useGettext()
|
||||
const loading = ref(false)
|
||||
const email = ref('')
|
||||
const confirm = ref(null)
|
||||
const errors = ref([])
|
||||
|
||||
async function showDialog(title, body, error = false) {
|
||||
let color = 'info'
|
||||
if (error) {
|
||||
color = 'error'
|
||||
}
|
||||
const result = await confirm.value.open(title, body, {
|
||||
color: color,
|
||||
cancelLabel: $gettext('Ok'),
|
||||
agreeLabel: $gettext('Return to login'),
|
||||
})
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
|
||||
async function recoverPassword() {
|
||||
const { valid } = await this.$refs.observer.validate()
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
email: email.value,
|
||||
}
|
||||
loading.value = true
|
||||
auth
|
||||
.recoverPassword(payload)
|
||||
.then(async (resp) => {
|
||||
loading.value = false
|
||||
if (resp.status === 200) {
|
||||
if (resp.data.type === 'sms') {
|
||||
router.push({ name: 'PasswordRecoverySms' })
|
||||
} else if (resp.data.type === 'email') {
|
||||
await showDialog(
|
||||
$gettext('Info'),
|
||||
$gettext('Email sent, please check your inbox')
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(async (err) => {
|
||||
loading.value = false
|
||||
if (err.response.status === 404) {
|
||||
if (
|
||||
err.response.data.type === 'sms' ||
|
||||
err.response.data.type === 'email'
|
||||
) {
|
||||
errors.value = [err.response.data.reason]
|
||||
}
|
||||
} else if (
|
||||
err.response.status === 503 &&
|
||||
err.response.data.type === 'email'
|
||||
) {
|
||||
await showDialog($gettext('Error'), err.response.data.reason, true)
|
||||
} else if (err.response.status === 429) {
|
||||
errors.value = [
|
||||
$gettext('Too many unsuccessful attempts, please try later.'),
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<template v-if="smAndDown">
|
||||
<PasswordRecoverySmsTotpForm />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row class="fill-height ma-0">
|
||||
<v-col class="bg-primary">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="300"
|
||||
class="mt-10 ml-8"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
<PasswordRecoverySmsTotpForm />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import PasswordRecoverySmsTotpForm from '@/components/login/PasswordRecoverySmsTotpForm.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { smAndDown } = useDisplay()
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<template v-if="smAndDown">
|
||||
<PasswordRecoveryForm />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row class="fill-height ma-0">
|
||||
<v-col class="bg-primary">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="300"
|
||||
class="mt-10 ml-8"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
<PasswordRecoveryForm />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import PasswordRecoveryForm from '@/components/login/PasswordRecoveryForm.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { smAndDown } = useDisplay()
|
||||
</script>
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<template v-if="smAndDown">
|
||||
<TwofaForm />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row class="fill-height ma-0">
|
||||
<v-col class="bg-primary">
|
||||
<v-img
|
||||
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
|
||||
max-width="300"
|
||||
class="mt-10 ml-8"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="text-center">
|
||||
<TwofaForm />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import TwofaForm from '@/components/login/TwofaForm.vue'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
const { smAndDown } = useDisplay()
|
||||
</script>
|
||||
@@ -206,7 +206,7 @@ OAUTH2_PROVIDER = {
|
||||
'OIDC_ENABLED': True,
|
||||
'OIDC_RP_INITIATED_LOGOUT_ENABLED': True,
|
||||
'OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT': True,
|
||||
'OIDC_RSA_PRIVATE_KEY': env('OIDC_RSA_PRIVATE_KEY'),
|
||||
'OIDC_RSA_PRIVATE_KEY': env.str('OIDC_RSA_PRIVATE_KEY', multiline=True),
|
||||
'SCOPES': {
|
||||
'openid': 'OpenID Connect scope',
|
||||
'read': 'Read scope',
|
||||
|
||||
+1
-1
@@ -53,4 +53,4 @@ rq>=1.13.0
|
||||
django-rq
|
||||
|
||||
# Sieve
|
||||
sievelib>=1.4
|
||||
sievelib>=1.4.1
|
||||
|
||||
Reference in New Issue
Block a user