mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
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
This commit is contained in:
+16
-3
@@ -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
|
||||
|
||||
@@ -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/"
|
||||
}
|
||||
|
||||
@@ -25,4 +25,7 @@ export default {
|
||||
deleteFido(id) {
|
||||
return repository.delete(`/fido/${id}/`)
|
||||
},
|
||||
editFido(id, data) {
|
||||
return repository.put(`/fido/${id}/`, data)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -92,11 +92,6 @@
|
||||
:label="$gettext('name')"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="editEnabled"
|
||||
:label="$gettext('enabled')"
|
||||
color="principal"
|
||||
/>
|
||||
</v-form>
|
||||
</ConfirmDialog>
|
||||
<v-dialog v-model="showBackupTokenDialog" max-width="800px" persistent>
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"] == ""
|
||||
|
||||
@@ -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}"
|
||||
}}
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user