Merge pull request #3274 from modoboa/fix/beta2_review

Fixes after review of beta2
This commit is contained in:
Antoine Nguyen
2024-07-18 13:33:06 +02:00
committed by GitHub
23 changed files with 137 additions and 833 deletions
+1 -1
View File
@@ -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>
+19 -15
View File
@@ -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
-22
View File
@@ -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>
+61 -53
View File
@@ -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>
-26
View File
@@ -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
View File
@@ -53,4 +53,4 @@ rq>=1.13.0
django-rq
# Sieve
sievelib>=1.4
sievelib>=1.4.1