Merge pull request #3732 from modoboa/fix/better_frontend_auth

Faster frontend authentication.
This commit is contained in:
Antoine Nguyen
2025-09-23 10:22:54 +02:00
committed by GitHub
13 changed files with 31 additions and 47 deletions
-3
View File
@@ -490,9 +490,6 @@ router.beforeEach(async (to, from, next) => {
const previousPage = window.location.href
sessionStorage.setItem('previousPage', previousPage)
const authStore = useAuthStore()
if (!authStore.authUser) {
await authStore.initialize()
}
const isAuth = await authStore.validateAccess()
if (isAuth) {
if (to.meta.allowedRoles !== undefined) {
+4 -13
View File
@@ -106,17 +106,6 @@ export const useAuthStore = defineStore('auth', () => {
})
}
async function initialize() {
if (isAuthenticated.value) {
return null
}
const user = await manager.getUser()
if (!user) {
return null
}
return fetchUser()
}
async function validateAccess() {
const user = await manager.getUser()
if (!user || user.expired) {
@@ -124,6 +113,9 @@ export const useAuthStore = defineStore('auth', () => {
}
repository.defaults.headers.common.Authorization = `Bearer ${user.access_token}`
repository.defaults.headers.post['Content-Type'] = 'application/json'
if (!isAuthenticated.value) {
await fetchUser()
}
return true
}
@@ -137,7 +129,7 @@ export const useAuthStore = defineStore('auth', () => {
async function completeLogin(redirectUrl) {
try {
const user = await manager.signinRedirectCallback()
const user = await manager.signinCallback()
isAuthenticated.value = true
const previousPage = sessionStorage.getItem('previousPage')
// Redirect the user to the previous page if available
@@ -204,7 +196,6 @@ export const useAuthStore = defineStore('auth', () => {
addFidoCred,
deleteFidoCreds,
editFidoCred,
initialize,
login,
$reset,
updateAccount,
@@ -1,5 +1,4 @@
<script setup>
import { onMounted } from 'vue'
import { useAuthStore } from '@/stores'
import { useModoboaTheme } from '@/composables/theme'
@@ -10,10 +9,7 @@ const props = defineProps({
const authStore = useAuthStore()
const { enableTheme } = useModoboaTheme()
onMounted(() => {
authStore.completeLogin(props.redirectUrl)
})
await authStore.completeLogin(props.redirectUrl)
await enableTheme()
</script>
+1 -1
View File
@@ -202,7 +202,7 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
return self.dnsrecord_set.filter(type="autodiscover").first()
@cached_property
def allocated_quota(self):
def allocated_quota(self) -> int:
"""Return current quota allocation."""
if not self.quota:
return 0
+3 -1
View File
@@ -1,5 +1,7 @@
"""Admin model mixins."""
from typing import Optional
from modoboa.policyd.utils import get_message_counter
@@ -18,7 +20,7 @@ class MessageLimitMixin:
return self.message_limit - get_message_counter(self.message_counter_key)
@property
def sent_messages_in_percent(self):
def sent_messages_in_percent(self) -> Optional[int]: # noqa
"""Return number of sent messages as a percentage."""
if not self.message_limit:
return None
+4 -4
View File
@@ -62,7 +62,7 @@ class ARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
def test_retrieve_armessage_domadmin(self):
admin = User.objects.get(username="admin@test.com")
self.client.logout()
self.client.force_login(admin)
self.client.force_authenticate(admin)
url = reverse("api:armessage-list")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -74,7 +74,7 @@ class ARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
def test_retrieve_armessage_simpleuser(self):
self.client.logout()
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
url = reverse("api:armessage-list")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
@@ -156,7 +156,7 @@ class AccountARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
response = self.client.get(url)
self.assertEqual(response.status_code, 403)
self.client.logout()
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
self.assertFalse(
models.ARmessage.objects.filter(mbox=self.account.mailbox).exists()
)
@@ -168,7 +168,7 @@ class AccountARMessageViewSetTestCase(PatcherMixin, ModoAPITestCase):
def test_set_armessage(self):
self.client.logout()
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
url = reverse("api:account_armessage-armessage")
fromdate = timezone.now()
todate = fromdate + relativedelta(days=4)
+8 -8
View File
@@ -13,7 +13,7 @@ from modoboa.admin import factories as admin_factories
from modoboa.admin import models as admin_models
from modoboa.core import factories as core_factories
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoTestCase, ModoAPITestCase
from modoboa.lib.tests import ModoAPITestCase
from modoboa.admin.factories import populate_database
@@ -53,7 +53,7 @@ class TestDataMixin:
cls.scalendar2 = factories.SharedCalendarFactory(domain=cls.domain2)
class AccessRuleTestCase(ModoTestCase):
class AccessRuleTestCase(ModoAPITestCase):
@classmethod
def setUpTestData(cls):
@@ -139,7 +139,7 @@ class UserCalendarViewSetTestCase(TestDataMixin, ModoAPITestCase):
def setUp(self):
"""Initiate test context."""
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
self.set_global_parameter("server_location", "http://localhost:5232")
def test_get_calendars(self):
@@ -214,7 +214,7 @@ class SharedCalendarViewSetTestCase(TestDataMixin, ModoAPITestCase):
def setUp(self):
"""Initiate test context."""
self.client.force_login(self.admin_account)
self.client.force_authenticate(self.admin_account)
self.set_global_parameter("server_location", "http://localhost:5232")
def test_get_calendars(self):
@@ -304,7 +304,7 @@ class AccessRuleViewSetTestCase(TestDataMixin, ModoAPITestCase):
def setUp(self):
"""Initiate test context."""
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
def test_get_accessrules(self):
"""Test access rule retrieval."""
@@ -383,7 +383,7 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase):
self.cal_mock.return_value = mocks.Calendar(client=self.client_mock)
self.addCleanup(patcher2.stop)
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
self.set_global_parameter("server_location", "http://localhost")
def test_get_user_events(self):
@@ -550,7 +550,7 @@ class AttendeeViewSetTestCase(ModoAPITestCase):
def setUp(self):
"""Initiate test context."""
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
def test_get_attendees(self):
"""Test attendees retrieval."""
@@ -571,7 +571,7 @@ class MailboxViewSetTestCase(ModoAPITestCase):
def setUp(self):
"""Initiate test context."""
self.client.force_login(self.account)
self.client.force_authenticate(self.account)
def test_get_mailboxes(self):
"""Test mailbox retrieval."""
+5 -7
View File
@@ -11,7 +11,7 @@ from django.utils import timezone
from modoboa.admin import factories as admin_factories
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoAPITestCase, ModoTestCase
from modoboa.lib.tests import ModoAPITestCase
from . import factories
from . import mocks
@@ -46,7 +46,7 @@ class TestDataMixin:
def setUp(self, *args, **kwargs):
"""Initiate test context."""
self.client.force_login(self.user)
self.client.force_authenticate(self.user)
self.set_global_parameter(
"server_location", "http://example.test/radicale/", app="calendars"
)
@@ -72,8 +72,7 @@ class ViewsTestCase(TestDataMixin, ModoAPITestCase):
def test_user_settings(self):
"""Check that remote collection creation request is sent."""
# 1. Addressbook with contacts must be synced manually
data = {"username": self.user.username, "password": "toto"}
self.client.post(reverse("core:login"), data)
self.client.force_authenticate(self.user)
self.enable_cdav_sync()
self.addressbook.refresh_from_db()
self.assertIs(self.addressbook.last_sync, None)
@@ -81,8 +80,7 @@ class ViewsTestCase(TestDataMixin, ModoAPITestCase):
# 2. Addressbook with no contacts can be considered synced
user = core_models.User.objects.get(username="user@test2.com")
abook = user.addressbook_set.first()
data = {"username": user.username, "password": "toto"}
self.client.post(reverse("core:login"), data)
self.client.force_authenticate(user)
abook.refresh_from_db()
self.assertIs(abook.last_sync, None)
# Now enable sync.
@@ -312,7 +310,7 @@ class EmailAddressViewSetTestCase(TestDataMixin, ModoAPITestCase):
self.assertEqual(len(response.data), 3)
class ImportTestCase(TestDataMixin, ModoTestCase):
class ImportTestCase(TestDataMixin, ModoAPITestCase):
def setUp(self):
super().setUp()
+2 -2
View File
@@ -1,9 +1,9 @@
from django.urls import reverse_lazy
from modoboa.lib.tests import ModoTestCase
from modoboa.lib.tests import ModoAPITestCase
class OpenAPITestCase(ModoTestCase):
class OpenAPITestCase(ModoAPITestCase):
openapi_schema_url = reverse_lazy("schema-v1-legacy")
def test_unauthorized(self):
@@ -235,7 +235,6 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
+1 -1
View File
@@ -220,7 +220,7 @@ class User(AbstractUser):
return self.email
@property
def tfa_enabled(self):
def tfa_enabled(self) -> bool:
return self.totp_enabled or self.webauthn_enabled
def is_owner(self, obj):
+2
View File
@@ -158,6 +158,8 @@ class ProfileTestCase(LDAPTestCaseMixin, ModoAPITestCase):
username = "testuser@example.com"
self.authenticate(username, "test")
user = models.User.objects.get(username=username)
self.client.force_authenticate(user)
response = self.client.post(
reverse("v2:account-set-me-password"),
{"password": "test", "new_password": "Toto1234"},
-1
View File
@@ -252,7 +252,6 @@ REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"oauth2_provider.contrib.rest_framework.OAuth2Authentication",
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",