From a5cee068324400b1ebd8b35cb90b2e28aa6d8131 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Fri, 12 Jul 2024 15:50:50 +0200 Subject: [PATCH] Fido fixes and updated changelog Rmeoved enabled parameter to fido objects for more simplicity, added logout redirect to frontend app, added edition to fido objects name through the frontend --- CHANGELOG.md | 19 +++++++-- frontend/public/config.json | 3 +- frontend/src/api/auth.js | 3 ++ .../src/components/account/FidoAuthForm.vue | 40 +++++++------------ frontend/src/stores/auth.store.js | 14 +++++++ modoboa/core/api/v2/serializers.py | 14 +++---- .../management/commands/load_initial_data.py | 7 +++- modoboa/core/migrations/0028_userfidokeys.py | 2 - modoboa/core/models.py | 1 - 9 files changed, 62 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36feb3a02..9a9ae976e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ ## Changelog -### 2.3.0 - Not yet released +### 2.3.0 + +#### New features + +* Sieve filter management has been added to the core repository and has been migrated to New Admin. +* Postifix auto reply has been added to the core repository and has been migrated to New Admin. +* Added Oauth2 authentication. +* Added Webauthn (Fido2) as a second authentication method. +* New Admin have been migrated to Vue 3. + +#### Fixes + +* Few security fixes (see our policy https://github.com/modoboa/modoboa/security/policy). +* Removed SORBS DNSBL provider since it has been shutdown (https://www.sorbs.net/). +* Few documentation update. +* Fixed some issues with the password scheme setting -* Added Oauth2 authentication -* Added Webauthn (Fido2) as a second authentication method diff --git a/frontend/public/config.json b/frontend/public/config.json index 7c7f4ef46..c3d464b21 100644 --- a/frontend/public/config.json +++ b/frontend/public/config.json @@ -3,5 +3,6 @@ "API_DOC_URL": "/api/schema-v2/swagger/", "OAUTH_AUTHORITY_URL": "https://localhost:8000/api/o", "OAUTH_CLIENT_ID": "LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD", - "OAUTH_REDIRECT_URI": "https://localhost:3000/login/logged" + "OAUTH_REDIRECT_URI": "https://localhost:3000/login/logged", + "OAUTH_POST_REDIRECT_URI": "https://localhost:3000/" } diff --git a/frontend/src/api/auth.js b/frontend/src/api/auth.js index 14beca4c3..3c37bfcf9 100644 --- a/frontend/src/api/auth.js +++ b/frontend/src/api/auth.js @@ -25,4 +25,7 @@ export default { deleteFido(id) { return repository.delete(`/fido/${id}/`) }, + editFido(id, data) { + return repository.put(`/fido/${id}/`, data) + }, } diff --git a/frontend/src/components/account/FidoAuthForm.vue b/frontend/src/components/account/FidoAuthForm.vue index 014c36f29..9325d0358 100644 --- a/frontend/src/components/account/FidoAuthForm.vue +++ b/frontend/src/components/account/FidoAuthForm.vue @@ -92,11 +92,6 @@ :label="$gettext('name')" :rules="[rules.required]" /> - @@ -139,14 +134,12 @@ const headers = [ { title: $gettext('Added On'), key: 'added_on' }, { title: $gettext('Last Used'), key: 'last_used' }, { title: $gettext('Use count'), key: 'use_count' }, - { title: $gettext('Enabled'), key: 'enabled' }, { title: $gettext('Actions'), key: 'actions' }, ] const confirmDeletion = ref() const confirmEdition = ref() const editForm = ref() const editName = ref('') -const editEnabled = ref(false) const tokens = ref() const showBackupTokenDialog = ref(false) @@ -159,21 +152,18 @@ async function startFidoRegistration() { const creationOption = await authApi.beginFidoRegistration() if (creationOption) { const options = parseCreationOptionsFromJSON(creationOption.data) - try { - const device = await create(options) - const result = device.toJSON() - result.name = name.value - authStore.addFidoCred(result).then((res) => { - if (res.status === 200) { - tokens.value = res.data.tokens - showBackupTokenDialog.value = true - } - }) - fidoForm.value.reset() - } catch { - registrationLoading.value = false - return - } + const device = await create(options) + const result = device.toJSON() + result.name = name.value + authStore.addFidoCred(result).then((res) => { + if (res.status === 200) { + tokens.value = res.data.tokens + showBackupTokenDialog.value = true + } + }) + fidoForm.value.reset() + registrationLoading.value = false + return } registrationLoading.value = false } @@ -205,7 +195,6 @@ async function checkForm() { async function editCred(cred) { editName.value = cred.name - editEnabled.value = cred.enabled const result = await confirmEdition.value.open( $gettext('Edition'), @@ -219,9 +208,8 @@ async function editCred(cred) { if (!result) { return } - - data = { name: editName.value, enabled: editEnabled.value } - //TODO : send it ! + const data = { name: editName.value } + await authStore.editFidoCred(cred.id, data) } onMounted(() => { diff --git a/frontend/src/stores/auth.store.js b/frontend/src/stores/auth.store.js index 1b9a0b46c..16774b4e9 100644 --- a/frontend/src/stores/auth.store.js +++ b/frontend/src/stores/auth.store.js @@ -21,6 +21,7 @@ export const useAuthStore = defineStore('auth', () => { authority: config.OAUTH_AUTHORITY_URL, client_id: config.OAUTH_CLIENT_ID, redirect_uri: config.OAUTH_REDIRECT_URI, + post_logout_redirect_uri: config.OAUTH_POST_REDIRECT_URI, response_type: 'code', scope: 'openid read write', automaticSilentRenew: true, @@ -71,6 +72,18 @@ export const useAuthStore = defineStore('auth', () => { }) } + async function editFidoCred(id, data) { + return authApi.editFido(id, data).then((res) => { + for (const i = 0; i < fidoCreds.value.length; i++) { + if (fidoCreds.value[i].id === id) { + fidoCreds.value[i] = res.data + break + } + } + return res + }) + } + function getAccessToken() { return manager.getUser().then((user) => { if (!user) { @@ -172,6 +185,7 @@ export const useAuthStore = defineStore('auth', () => { getFidoCreds, addFidoCred, deleteFidoCreds, + editFidoCred, initialize, login, $reset, diff --git a/modoboa/core/api/v2/serializers.py b/modoboa/core/api/v2/serializers.py index 5b7c23202..aa40d674b 100644 --- a/modoboa/core/api/v2/serializers.py +++ b/modoboa/core/api/v2/serializers.py @@ -241,8 +241,13 @@ class CoreGlobalParametersSerializer(serializers.Serializer): class FIDOSerializer(serializers.ModelSerializer): class Meta: model = models.UserFidoKeys - fields = ["id", "name", "enabled", "added_on", "last_used", "use_count"] - # TODO : read-only fields for everything other than name and enabled :) + fields = ["id", "name", "added_on", "last_used", "use_count"] + extra_kwargs = { + "id": {"read_only": True}, + "added_on": {"read_only": True}, + "last_used": {"read_only": True}, + "use_count": {"read_only": True}, + } class FidoRegistrationSerializer(serializers.Serializer): @@ -368,7 +373,6 @@ def send_sms_code(secret, backend, user): class PasswordRecoverySmsSerializer(serializers.Serializer): - email = serializers.EmailField() def validate(self, data, *args, **kwargs): @@ -404,7 +408,6 @@ class PasswordRecoverySmsSerializer(serializers.Serializer): class PasswordRecoverySmsConfirmSerializer(serializers.Serializer): - sms_totp = serializers.CharField(min_length=6, max_length=6) def validate(self, data): @@ -436,7 +439,6 @@ class PasswordRecoverySmsConfirmSerializer(serializers.Serializer): class PasswordRecoverySmsResendSerializer(serializers.Serializer): - def validate(self, data): try: self.context["totp_secret"] = self.context["request"].session["totp_secret"] @@ -463,7 +465,6 @@ class PasswordRecoverySmsResendSerializer(serializers.Serializer): class PasswordRecoveryConfirmSerializer(serializers.Serializer): - new_password1 = serializers.CharField() new_password2 = serializers.CharField() @@ -481,7 +482,6 @@ class PasswordRecoveryConfirmSerializer(serializers.Serializer): return user def validate(self, data): - # Validate password if ( data["new_password1"] == "" diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index 1ef969cda..ae51e60b3 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -114,10 +114,12 @@ class Command(BaseCommand): allowed_host = "localhost" frontend_application = app_model.objects.filter(name="Modoboa frontend") frontend_path = getattr(settings, "NEW_ADMIN_URL", "new-admin") - redirect_uri = f"https://{allowed_host}/{frontend_path}/login/logged" + base_uri = f"https://{allowed_host}/{frontend_path}" + redirect_uri = f"{base_uri}/login/logged" client_id = "" if not frontend_application.exists(): if options["dev"]: + base_uri = "https://localhost:3000/" redirect_uri = "https://localhost:3000/login/logged" client_id = "LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD" else: @@ -128,6 +130,8 @@ class Command(BaseCommand): f"--redirect-uris={redirect_uri}", "--name='Modoboa frontend'", f"--client-id={client_id}", + f"--post-logout-redirect-uris={base_uri}", + "--skip-authorization", "public", "authorization-code", ) @@ -147,6 +151,7 @@ class Command(BaseCommand): "OAUTH_AUTHORITY_URL": "https://{allowed_host}/api/o", "OAUTH_CLIENT_ID": "{client_id}", "OAUTH_REDIRECT_URI": "{redirect_uri}" + "OAUTH_POST_REDIRECT_URI": "{base_uri}" }} """ ) diff --git a/modoboa/core/migrations/0028_userfidokeys.py b/modoboa/core/migrations/0028_userfidokeys.py index 2ee9150c0..aae5f240e 100644 --- a/modoboa/core/migrations/0028_userfidokeys.py +++ b/modoboa/core/migrations/0028_userfidokeys.py @@ -6,7 +6,6 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ ("core", "0027_alter_user_language"), ] @@ -25,7 +24,6 @@ class Migration(migrations.Migration): ), ), ("name", models.CharField(max_length=255)), - ("enabled", models.BooleanField(default=True)), ("added_on", models.DateTimeField(auto_now_add=True)), ("last_used", models.DateTimeField(default=None, null=True)), ("use_count", models.IntegerField(default=0)), diff --git a/modoboa/core/models.py b/modoboa/core/models.py index c509cc150..42041efe0 100644 --- a/modoboa/core/models.py +++ b/modoboa/core/models.py @@ -420,7 +420,6 @@ reversion.register(User) class UserFidoKeys(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) name = models.CharField(max_length=255) - enabled = models.BooleanField(default=True) added_on = models.DateTimeField(auto_now_add=True) last_used = models.DateTimeField(null=True, default=None) use_count = models.IntegerField(default=0)