mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Added customization to webauthn keys
This commit is contained in:
@@ -22,4 +22,7 @@ export default {
|
||||
getAllFidoRegistred() {
|
||||
return repository.get('/fido/')
|
||||
},
|
||||
deleteFido(id) {
|
||||
return repository.delete(`/fido/${id}/`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,51 @@
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div tag="p" class="my-4">
|
||||
<template v-if="fidoCreds.length === 0">
|
||||
<div tag="p" class="my-4">
|
||||
{{
|
||||
$gettext(
|
||||
"You don't have any WebAuthN registered as a second authentication method."
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h3>{{ $gettext("Registered Webauthn devices") }}</h3>
|
||||
<v-data-table-virtual
|
||||
:headers="headers"
|
||||
:items="fidoCreds"
|
||||
height="400"
|
||||
item-value="id"
|
||||
>
|
||||
<template #[`item.added_on`]="{ item }">
|
||||
{{ $date(item.added_on) }}
|
||||
</template>
|
||||
<template #[`item.last_used`]="{ item }">
|
||||
<template v-if="item.last_used === null">
|
||||
{{ $gettext('Never') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $date(item.last_used) }}
|
||||
</template>
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon
|
||||
class="me-2"
|
||||
size="small"
|
||||
@click="editCred(item)"
|
||||
>
|
||||
mdi-pencil
|
||||
</v-icon>
|
||||
<v-icon
|
||||
size="small"
|
||||
@click="deleteCred(item)"
|
||||
>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table-virtual>
|
||||
</template>
|
||||
<template v-if="browserCapable">
|
||||
<v-form ref="fidoForm" @submit.prevent="startFidoRegistration">
|
||||
<v-text-field
|
||||
@@ -34,13 +72,24 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
<ConfirmDialog ref="confirmDeletion"/>
|
||||
<ConfirmDialog ref="confirmEdition">
|
||||
<v-form ref="editForm">
|
||||
<v-text-field v-model="editName" :label="$gettext('name')"/>
|
||||
<v-switch v-model="editEnabled" :label="$gettext('enabled')" color="principal"/>
|
||||
</v-form>
|
||||
</ConfirmDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import authApi from '@/api/auth.js'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import rules from '@/plugins/rules.js'
|
||||
import { useAuthStore } from '@/stores'
|
||||
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
@@ -49,6 +98,21 @@ const registrationLoading = ref(false)
|
||||
const creationOption = ref()
|
||||
const browserCapable = !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential)
|
||||
const fidoForm = ref()
|
||||
const fidoCreds = computed(() => authStore.fidoCreds)
|
||||
const headers = [
|
||||
{ title: '#', key: 'id' },
|
||||
{ title: $gettext('Name'), key: 'name' },
|
||||
{ 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)
|
||||
|
||||
async function startFidoRegistration() {
|
||||
const { valid } = await fidoForm.value.validate()
|
||||
@@ -60,19 +124,48 @@ async function startFidoRegistration() {
|
||||
.then(function (attestation) {
|
||||
const result = createResponseToJSON(attestation)
|
||||
result.name = name.value
|
||||
console.log(result)
|
||||
authApi.endFidoRegistration(result).then((resp) => {
|
||||
console.log(resp)
|
||||
})
|
||||
authStore.addFidoCred(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCred(cred) {
|
||||
const result = await confirmDeletion.value.open(
|
||||
$gettext('Warning'),
|
||||
$gettext('Do you really want to delete this WebaAuthn device?'),
|
||||
{
|
||||
color: 'warning',
|
||||
cancelLabel: $gettext('No'),
|
||||
agreeLabel: $gettext('Yes'),
|
||||
}
|
||||
)
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
authStore.deleteFidoCreds(cred.id)
|
||||
}
|
||||
|
||||
async function editCred(cred) {
|
||||
editName.value = cred.name
|
||||
editEnabled.value = cred.enabled
|
||||
const result = await confirmEdition.value.open(
|
||||
$gettext('Edition'),
|
||||
$gettext('Edit your device'),
|
||||
{
|
||||
color: 'warning',
|
||||
cancelLabel: $gettext('Cancel'),
|
||||
agreeLabel: $gettext('Save'),
|
||||
}
|
||||
)
|
||||
if (!result) {
|
||||
return
|
||||
}
|
||||
console.log(editForm.value.items)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
registrationLoading.value = true
|
||||
authApi.getAllFidoRegistred().then((resp) => {
|
||||
console.log(resp)
|
||||
})
|
||||
authStore.getFidoCreds()
|
||||
authApi.beginFidoRegistration().then((resp) => {
|
||||
creationOption.value = createRequestFromJSON(resp.data)
|
||||
}).finally(() => registrationLoading.value = false)
|
||||
|
||||
@@ -10,7 +10,7 @@ export default {
|
||||
}
|
||||
app.config.globalProperties.$date = (value) => {
|
||||
return DateTime.fromISO(value)
|
||||
.setLocale('en')
|
||||
.setLocale(gettext.current)
|
||||
.toLocaleString(DateTime.DATETIME_MED)
|
||||
}
|
||||
app.config.globalProperties.$truncate = (value, length, clamp) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { UserManager } from 'oidc-client-ts'
|
||||
import repository from '@/api/repository'
|
||||
import accountApi from '@/api/account'
|
||||
import accountsApi from '@/api/accounts'
|
||||
import authApi from '@/api/auth.js'
|
||||
|
||||
import { useGlobalConfig } from '@/main'
|
||||
|
||||
@@ -28,6 +29,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
filterProtocolClaims: true,
|
||||
loadUserInfo: true,
|
||||
})
|
||||
const fidoCreds = ref([])
|
||||
|
||||
const userHasMailbox = computed(() => {
|
||||
return authUser.value.mailbox !== null
|
||||
@@ -41,6 +43,24 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
})
|
||||
}
|
||||
|
||||
async function getFidoCreds() {
|
||||
return authApi.getAllFidoRegistred().then((resp) => {
|
||||
fidoCreds.value = resp.data
|
||||
})
|
||||
}
|
||||
|
||||
async function addFidoCred(result) {
|
||||
return authApi.endFidoRegistration(result).then(() => {
|
||||
getFidoCreds()
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteFidoCreds(id) {
|
||||
return authApi.deleteFido(id).then(() => {
|
||||
fidoCreds.value = fidoCreds.value.filter((cred) => cred.id !== id)
|
||||
})
|
||||
}
|
||||
|
||||
function getAccessToken() {
|
||||
return manager.getUser().then((user) => {
|
||||
if (!user) {
|
||||
@@ -138,9 +158,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
completeLogin,
|
||||
isAuthenticated,
|
||||
userHasMailbox,
|
||||
fidoCreds,
|
||||
validateAccess,
|
||||
fetchUser,
|
||||
getAccessToken,
|
||||
getFidoCreds,
|
||||
addFidoCred,
|
||||
deleteFidoCreds,
|
||||
initialize,
|
||||
login,
|
||||
$reset,
|
||||
|
||||
@@ -249,6 +249,7 @@ 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 :)
|
||||
|
||||
|
||||
class FidoRegistrationSerializer(serializers.Serializer):
|
||||
|
||||
@@ -27,7 +27,7 @@ def delete_cache_key(class_target, throttles, request):
|
||||
"""Attempt to delete cache key from throttling on login/password reset success."""
|
||||
|
||||
for throttle in throttles:
|
||||
if type(throttle) is class_target:
|
||||
if type(throttle) == class_target:
|
||||
throttle.reset_cache(request)
|
||||
return
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ class FIDOViewSet(GetThrottleViewsetMixin,
|
||||
if not request.user.tfa_enabled:
|
||||
request.user.tfa_enabled = True
|
||||
request.user.save()
|
||||
return response.Response({"success": True}, 200)
|
||||
return response.Response(status=204)
|
||||
|
||||
@action(methods=["post"], detail=False, url_path="authenticate/begin")
|
||||
def authenticate_begin(self, request):
|
||||
|
||||
Reference in New Issue
Block a user