mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
User forward form
This commit is contained in:
@@ -28,5 +28,11 @@ export default {
|
||||
},
|
||||
resetRecoveryCodes () {
|
||||
return repository.post(`${resource}/tfa/reset_codes/`)
|
||||
},
|
||||
getForward () {
|
||||
return repository.get(`${resource}/forward/`)
|
||||
},
|
||||
setForward (data) {
|
||||
return repository.post(`${resource}/forward/`, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,11 @@ export default {
|
||||
this.$emit('close')
|
||||
},
|
||||
submit () {
|
||||
accounts.create(this.account).then(resp => {
|
||||
const data = { ...this.account }
|
||||
if (this.account.role === 'SuperAdmins' && this.account.username.indexOf('@') === -1) {
|
||||
delete data.mailbox
|
||||
}
|
||||
accounts.create(data).then(resp => {
|
||||
bus.$emit('notification', { msg: this.$gettext('Account created') })
|
||||
this.$emit('created')
|
||||
this.close()
|
||||
|
||||
@@ -178,7 +178,7 @@ export default {
|
||||
account: {
|
||||
handler: function (val) {
|
||||
if (val) {
|
||||
this.editedAccount = JSON.parse(JSON.stringify(val))
|
||||
this.editedAccount = { ...val }
|
||||
delete this.editedAccount.domains
|
||||
if (this.editedAccount.mailbox === null) {
|
||||
this.editedAccount.mailbox = {}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<validation-provider
|
||||
v-slot="{ errors }"
|
||||
rules="required"
|
||||
vid="username"
|
||||
>
|
||||
<label class="m-label">{{ $gettext('Username') }}</label>
|
||||
<email-field
|
||||
@@ -12,8 +11,6 @@
|
||||
:error-messages="errors"
|
||||
:placeholder="usernamePlaceholder"
|
||||
:type="usernameInputType"
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
</validation-provider>
|
||||
<label class="m-label">{{ $gettext('First name') }}</label>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<v-list>
|
||||
<template v-for="item in menuItems">
|
||||
<template v-if="item.roles === undefined || item.roles.indexOf(authUser.role) !== -1">
|
||||
<template v-if="displayMenuItem(item)">
|
||||
<v-list-item
|
||||
v-if="!item.children"
|
||||
class="menu-item"
|
||||
@@ -213,13 +213,13 @@ export default {
|
||||
},
|
||||
{
|
||||
text: this.$gettext('Preferences'),
|
||||
to: '',
|
||||
icon: 'mdi-tune',
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
text: this.$gettext('Forward'),
|
||||
to: '',
|
||||
condition: () => this.authUser.mailbox !== null,
|
||||
to: { name: 'UserForward' },
|
||||
icon: 'mdi-forward',
|
||||
exact: true
|
||||
}
|
||||
@@ -250,6 +250,9 @@ export default {
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
displayMenuItem (item) {
|
||||
return (item.roles === undefined || item.roles.indexOf(this.authUser.role) !== -1) && (item.condition === undefined || item.condition())
|
||||
},
|
||||
logout () {
|
||||
this.$store.dispatch('auth/logout').then(() => {
|
||||
this.$router.push({ name: 'Login' })
|
||||
|
||||
@@ -107,7 +107,6 @@ export default {
|
||||
return
|
||||
}
|
||||
}
|
||||
this.$emit('close')
|
||||
this.currentStep = 1
|
||||
this.steps.forEach((item, index) => {
|
||||
const observer = this.formObserverGetter(index + 1)
|
||||
@@ -115,6 +114,7 @@ export default {
|
||||
observer.reset()
|
||||
}
|
||||
})
|
||||
this.$emit('close')
|
||||
},
|
||||
async goToNextStep (current, next) {
|
||||
const observer = this.formObserverGetter(current)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
v-model="input"
|
||||
outlined
|
||||
dense
|
||||
autocomplete="new-password"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@input="update"
|
||||
@@ -150,6 +151,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
value (newValue) {
|
||||
console.log(newValue)
|
||||
this.input = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<label class="m-label"><translate>Recipient(s)</translate></label>
|
||||
<v-textarea
|
||||
v-model="form.recipients"
|
||||
:hint="'Indicate one or more recipients separated by a comma (,)'|translate"
|
||||
persistent-hint
|
||||
rows="3"
|
||||
outlined
|
||||
/>
|
||||
<v-checkbox
|
||||
v-model="form.keepcopies"
|
||||
:label="'Keep local copies'|translate"
|
||||
:hint="'Forward messages and store copies into your local mailbox'|translate"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pa-4">
|
||||
<v-btn
|
||||
color="success"
|
||||
:loading="loading"
|
||||
@click="submit"
|
||||
>
|
||||
<translate>Update</translate>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import account from '@/api/account'
|
||||
import { bus } from '@/main'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
account: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: {},
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit () {
|
||||
this.loading = true
|
||||
account.setForward(this.form).then(resp => {
|
||||
this.loading = false
|
||||
bus.$emit('notification', { type: 'success', msg: this.$gettext('Forward updated') })
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
account.getForward().then(resp => {
|
||||
this.form = resp.data
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -115,6 +115,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
console.log('profile')
|
||||
this.initFormFromAccount(this.account)
|
||||
languages.getAll().then(resp => {
|
||||
this.languages = resp.data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card>
|
||||
<v-card flat>
|
||||
<v-card-title>
|
||||
<span class="text-subtitle-1"><translate>Two factor authentication</translate></span>
|
||||
</v-card-title>
|
||||
|
||||
@@ -127,6 +127,15 @@ const routes = [
|
||||
requiresAuth: true,
|
||||
layout: 'user'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/user/forward',
|
||||
name: 'UserForward',
|
||||
component: () => import('../views/user/Forward.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
layout: 'user'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title><translate>Forward</translate></v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<div class="pa-4">
|
||||
<forward-form :account="authUser" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import ForwardForm from '@/components/user/ForwardForm'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ForwardForm
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
authUser: 'auth/authUser'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-toolbar {
|
||||
background-color: #f7f8fa !important;
|
||||
}
|
||||
</style>
|
||||
+12
-7
@@ -2110,9 +2110,9 @@ aws4@^1.8.0:
|
||||
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
|
||||
|
||||
axios@^0.21.2:
|
||||
version "0.21.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.2.tgz#21297d5084b2aeeb422f5d38e7be4fbb82239017"
|
||||
integrity sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==
|
||||
version "0.21.4"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
|
||||
integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==
|
||||
dependencies:
|
||||
follow-redirects "^1.14.0"
|
||||
|
||||
@@ -4591,11 +4591,16 @@ flush-write-stream@^1.0.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0, follow-redirects@^1.14.0:
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.14.4"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379"
|
||||
integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==
|
||||
|
||||
follow-redirects@^1.14.0:
|
||||
version "1.14.7"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
|
||||
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@@ -9772,9 +9777,9 @@ vuetify-loader@^1.3.0:
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
vuetify@^2.2.11:
|
||||
version "2.5.6"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-2.5.6.tgz#9cbb1eacece6c42028216312b9be23e35a7f5cf4"
|
||||
integrity sha512-2T8ML5PYuJ/AdMVH3ZIvzHnsM0nX8t4Xzj+0HFFGfLT7jLlptCFpG+JE8+kyrgGZlbUgulyOwCUu8hJkVsReFA==
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-2.6.2.tgz#1ed4d3c9e734d55202bd566b789cce0e0013279a"
|
||||
integrity sha512-nx3uZkO8MZNMshUEh1xKaQ1hQYepNwWFn3FVxKt+XBVf7ZFscd0GS/a3KZo4B3baXQmziCQAZKNIQF5IWeaIUw==
|
||||
|
||||
vuex@^3.4.0:
|
||||
version "3.6.2"
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core import validators as dj_validators
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from django.contrib.auth import password_validation
|
||||
@@ -383,3 +384,20 @@ class AliasSerializer(v1_serializers.AliasSerializer):
|
||||
for field in v1_serializers.AliasSerializer.Meta.fields
|
||||
if field != "internal"
|
||||
) + ("expire_at", "description")
|
||||
|
||||
|
||||
class UserForwardSerializer(serializers.Serializer):
|
||||
"""Serializer to define user forward."""
|
||||
|
||||
recipients = serializers.CharField(required=False)
|
||||
keepcopies = serializers.BooleanField(default=False)
|
||||
|
||||
def validate_recipients(self, value):
|
||||
value = value.strip()
|
||||
recipients = []
|
||||
if not value:
|
||||
return recipients
|
||||
for recipient in value.split(","):
|
||||
dj_validators.validate_email(recipient)
|
||||
recipients.append(recipient)
|
||||
return recipients
|
||||
|
||||
@@ -17,5 +17,7 @@ router.register(
|
||||
basename="sender_address")
|
||||
router.register(
|
||||
r"identities", viewsets.IdentityViewSet, basename="identities")
|
||||
router.register(
|
||||
r"account", viewsets.UserAccountViewSet, basename="account")
|
||||
|
||||
urlpatterns = router.urls
|
||||
|
||||
@@ -212,3 +212,56 @@ class AliasViewSet(v1_viewsets.AliasViewSet):
|
||||
return response.Response({
|
||||
"address": models.Alias.generate_random_address()
|
||||
})
|
||||
|
||||
|
||||
class UserAccountViewSet(viewsets.ViewSet):
|
||||
"""Viewset for current user operations."""
|
||||
|
||||
@action(methods=["get", "post"], detail=False)
|
||||
def forward(self, request, **kwargs):
|
||||
"""Get or define user forward."""
|
||||
mb = request.user.mailbox
|
||||
alias = models.Alias.objects.filter(
|
||||
address=mb.full_address, internal=False).first()
|
||||
data = {}
|
||||
if request.method == "GET":
|
||||
if alias is not None and alias.recipients:
|
||||
recipients = list(alias.recipients)
|
||||
if alias.aliasrecipient_set.filter(r_mailbox=mb).exists():
|
||||
data["keepcopies"] = True
|
||||
recipients.remove(mb.full_address)
|
||||
data["recipients"] = "\n".join(recipients)
|
||||
serializer = serializers.UserForwardSerializer(data)
|
||||
return response.Response(serializer.data)
|
||||
serializer = serializers.UserForwardSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
recipients = serializer.validated_data.get("recipients")
|
||||
if not recipients:
|
||||
models.Alias.objects.filter(
|
||||
address=mb.full_address, internal=False).delete()
|
||||
# Make sure internal self-alias is enabled
|
||||
models.Alias.objects.filter(
|
||||
address=mb.full_address, internal=True
|
||||
).update(enabled=True)
|
||||
else:
|
||||
if alias is None:
|
||||
alias = models.Alias.objects.create(
|
||||
address=mb.full_address,
|
||||
domain=mb.domain,
|
||||
enabled=mb.user.is_active
|
||||
)
|
||||
alias.post_create(request.user)
|
||||
if serializer.validated_data["keepcopies"]:
|
||||
# Make sure internal self-alias is enabled
|
||||
models.Alias.objects.filter(
|
||||
address=mb.full_address, internal=True
|
||||
).update(enabled=True)
|
||||
recipients.append(mb.full_address)
|
||||
else:
|
||||
# Deactivate internal self-alias to avoid storing
|
||||
# local copies...
|
||||
models.AliasAlias.objects.filter(
|
||||
address=mb.full_address, internal=True
|
||||
).update(enabled=False)
|
||||
alias.set_recipients(recipients)
|
||||
return response.Response(serializer.validated_data)
|
||||
|
||||
@@ -5,7 +5,6 @@ import io
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
|
||||
import django_otp
|
||||
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import filters, permissions, response, viewsets
|
||||
|
||||
@@ -158,7 +158,7 @@ WSGI_APPLICATION = 'test_project.wsgi.application'
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/2.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
LANGUAGE_CODE = 'en'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
@@ -279,6 +279,10 @@ LOGGING = {
|
||||
'syslog': {
|
||||
'format': '%(name)s: %(levelname)s %(message)s'
|
||||
},
|
||||
'django.server': {
|
||||
'()': 'django.utils.log.ServerFormatter',
|
||||
'format': '[%(server_time)s] %(message)s',
|
||||
}
|
||||
},
|
||||
'handlers': {
|
||||
'mail-admins': {
|
||||
@@ -296,7 +300,12 @@ LOGGING = {
|
||||
},
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler'
|
||||
}
|
||||
},
|
||||
'django.server': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'django.server',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'django': {
|
||||
@@ -314,6 +323,11 @@ LOGGING = {
|
||||
'level': 'INFO',
|
||||
'propagate': False
|
||||
},
|
||||
'django.server': {
|
||||
'handlers': ['django.server'],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
},
|
||||
# 'django_auth_ldap': {
|
||||
# 'level': 'DEBUG',
|
||||
# 'handlers': ['console']
|
||||
|
||||
Reference in New Issue
Block a user