User forward form

This commit is contained in:
Antoine Nguyen
2022-01-12 11:46:10 +01:00
parent 884735b9fe
commit cc1ae428c4
18 changed files with 227 additions and 20 deletions
+6
View File
@@ -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>
+6 -3
View File
@@ -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>
+9
View File
@@ -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'
}
}
]
+32
View File
@@ -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
View File
@@ -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"
+18
View File
@@ -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
+2
View File
@@ -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
+53
View File
@@ -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)
-1
View File
@@ -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
+16 -2
View File
@@ -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']