mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Allow to enable only webauthn
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"API_BASE_URL": "/api/v2/",
|
||||
"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"
|
||||
"API_BASE_URL": "/api/v2/",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -24,5 +24,5 @@ export default {
|
||||
},
|
||||
deleteFido(id) {
|
||||
return repository.delete(`/fido/${id}/`)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card flat>
|
||||
<v-card-title>
|
||||
<span class="text-h6">
|
||||
{{ $gettext('Backup codes') }}
|
||||
</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<template v-if="tfa_enabled">
|
||||
<v-alert type="info" class="mb-2">
|
||||
{{
|
||||
$gettext(
|
||||
"You already have generated backup codes for this account. If you don't remember them or have used them all, you generate new ones."
|
||||
)
|
||||
}}
|
||||
</v-alert>
|
||||
<v-form ref="editTFAForm">
|
||||
<label class="m-label">{{ $gettext('Password') }} </label>
|
||||
<v-text-field
|
||||
v-model="password"
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
:rules="[rules.required]"
|
||||
:error-messages="passwordError"
|
||||
/>
|
||||
<div class="mt-5">
|
||||
<v-btn
|
||||
:loading="loadingReset"
|
||||
color="success"
|
||||
@click="resetRecoveryCodes"
|
||||
>
|
||||
{{ $gettext('Reset recovery codes') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div tag="p" class="my-4">
|
||||
{{
|
||||
$gettext(
|
||||
'No two-factor authentificator is yet set for your account.'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</template>
|
||||
<v-dialog v-model="showCodesResetDialog" max-width="800px" persistent>
|
||||
<RecoveryCodesResetDialog
|
||||
:tokens="newTokens"
|
||||
@close="closeRecoveryCodesResetDialog"
|
||||
/>
|
||||
</v-dialog>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import accountApi from '@/api/account'
|
||||
import RecoveryCodesResetDialog from './RecoveryCodesResetDialog'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import { useBusStore, useAuthStore } from '@/stores'
|
||||
import rules from '@/plugins/rules'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
const busStore = useBusStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const tfa_enabled = computed(() => authStore.authUser.tfa_enabled)
|
||||
|
||||
const loadingReset = ref(false)
|
||||
const editTFAForm = ref()
|
||||
const password = ref('')
|
||||
const newTokens = ref([])
|
||||
const passwordError = ref()
|
||||
const showCodesResetDialog = ref(false)
|
||||
|
||||
async function resetRecoveryCodes() {
|
||||
const isValid = await editTFAForm.value.validate()
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
password: password.value,
|
||||
}
|
||||
loadingReset.value = true
|
||||
|
||||
accountApi
|
||||
.resetRecoveryCodes(payload)
|
||||
.then((response) => {
|
||||
newTokens.value = response.data.tokens
|
||||
showCodesResetDialog.value = true
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 400) {
|
||||
passwordError.value = error.response.data.password
|
||||
} else {
|
||||
busStore.displayNotification({
|
||||
msg: $gettext(error.response.data),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loadingReset.value = false
|
||||
editTFAForm.value.reset()
|
||||
})
|
||||
}
|
||||
|
||||
function closeRecoveryCodesResetDialog() {
|
||||
newTokens.value = []
|
||||
showCodesResetDialog.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -16,33 +16,8 @@
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<template v-if="browserCapable">
|
||||
<v-form ref="fidoForm" @submit.prevent="startFidoRegistration">
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
:label="$gettext('New device name')"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-btn
|
||||
color="success"
|
||||
type="submit"
|
||||
:loading="registrationLoading"
|
||||
>
|
||||
{{ $gettext('Setup device') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-alert type="error">
|
||||
{{
|
||||
$gettext(
|
||||
'Your browser does not seem compatible with WebAuthN'
|
||||
)
|
||||
}}
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-col>
|
||||
<v-col v-if="fidoCreds.length" cols="8">
|
||||
<v-col v-else cols="8">
|
||||
<h3>{{ $gettext('Registered Webauthn devices') }}</h3>
|
||||
<v-data-table-virtual
|
||||
:headers="headers"
|
||||
@@ -74,6 +49,38 @@
|
||||
</template>
|
||||
</v-data-table-virtual>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<template v-if="browserCapable">
|
||||
<h3>
|
||||
{{
|
||||
$gettext('Enter a name to register a new webauthn device.')
|
||||
}}
|
||||
</h3>
|
||||
<v-form ref="fidoForm" @submit.prevent="startFidoRegistration">
|
||||
<v-text-field
|
||||
v-model="name"
|
||||
:label="$gettext('New device name')"
|
||||
:rules="[rules.required]"
|
||||
/>
|
||||
<v-btn
|
||||
color="success"
|
||||
type="submit"
|
||||
:loading="registrationLoading"
|
||||
>
|
||||
{{ $gettext('Setup device') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-alert type="error">
|
||||
{{
|
||||
$gettext(
|
||||
'Your browser does not seem compatible with WebAuthN'
|
||||
)
|
||||
}}
|
||||
</v-alert>
|
||||
</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -95,7 +102,7 @@
|
||||
</ConfirmDialog>
|
||||
<v-dialog v-model="showBackupTokenDialog" max-width="800px" persistent>
|
||||
<RecoveryCodesResetDialog
|
||||
:tokens="newTokens"
|
||||
:tokens="tokens"
|
||||
@close="closeRecoveryCodesResetDialog"
|
||||
/>
|
||||
</v-dialog>
|
||||
@@ -153,16 +160,21 @@ async function startFidoRegistration() {
|
||||
const creationOption = await authApi.beginFidoRegistration()
|
||||
if (creationOption) {
|
||||
const options = parseCreationOptionsFromJSON(creationOption.data)
|
||||
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()
|
||||
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
|
||||
}
|
||||
}
|
||||
registrationLoading.value = false
|
||||
}
|
||||
|
||||
+24
-72
@@ -47,7 +47,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-else-if="tokens.length">
|
||||
<template v-else-if="successSetup">
|
||||
<v-alert type="success">
|
||||
{{
|
||||
$gettext(
|
||||
@@ -55,22 +55,24 @@
|
||||
)
|
||||
}}
|
||||
</v-alert>
|
||||
<p class="mt-4">
|
||||
{{
|
||||
$gettext(
|
||||
"The following recovery codes can be used one time each to let you regain access to your account, in case you lose your phone for example. Make sure to save them in a safe place, otherwise you won't be able to access your account anymore."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<v-table density="compact">
|
||||
<tbody>
|
||||
<tr v-for="token in tokens" :key="token">
|
||||
<td>{{ token }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<template v-if="tokens.length">
|
||||
<p class="mt-4">
|
||||
{{
|
||||
$gettext(
|
||||
"The following recovery codes can be used one time each to let you regain access to your account, in case you lose your phone for example. Make sure to save them in a safe place, otherwise you won't be able to access your account anymore."
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<v-table density="compact">
|
||||
<tbody>
|
||||
<tr v-for="token in tokens" :key="token">
|
||||
<td>{{ token }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="tfa_enabled">
|
||||
<template v-else-if="totp_enabled">
|
||||
<v-alert type="info" class="mb-2">
|
||||
{{
|
||||
$gettext(
|
||||
@@ -97,9 +99,6 @@
|
||||
>
|
||||
{{ $gettext('Disable 2FA') }}
|
||||
</v-btn>
|
||||
<v-btn :loading="loadingReset" @click="resetRecoveryCodes">
|
||||
{{ $gettext('Reset recovery codes') }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-form>
|
||||
</template>
|
||||
@@ -117,18 +116,11 @@
|
||||
</template>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-dialog v-model="showCodesResetDialog" max-width="800px" persistent>
|
||||
<RecoveryCodesResetDialog
|
||||
:tokens="newTokens"
|
||||
@close="closeRecoveryCodesResetDialog"
|
||||
/>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="js">
|
||||
import accountApi from '@/api/account'
|
||||
import RecoveryCodesResetDialog from './RecoveryCodesResetDialog'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { useBusStore, useAuthStore } from '@/stores'
|
||||
import { ref, computed } from 'vue'
|
||||
@@ -140,21 +132,18 @@ const busStore = useBusStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const editTFAForm = ref()
|
||||
|
||||
const newTokens = ref([])
|
||||
const pinCode = ref('')
|
||||
const pinCodeErrors = ref([])
|
||||
const key = ref(null)
|
||||
const qrURL = ref(null)
|
||||
const clicked = ref(false)
|
||||
const showCodesResetDialog = ref(false)
|
||||
const loadingDisable = ref(false)
|
||||
const loadingReset = ref(false)
|
||||
const password = ref('')
|
||||
const passwordError = ref([])
|
||||
const tokens = ref([])
|
||||
const successSetup = ref(false)
|
||||
|
||||
const tfa_enabled = computed(() => authStore.authUser.tfa_enabled)
|
||||
const totp_enabled = computed(() => authStore.authUser.totp_enabled)
|
||||
|
||||
function getKey() {
|
||||
accountApi.getKeyForTFASetup().then((resp) => {
|
||||
@@ -180,7 +169,10 @@ function finalizeTFASetup() {
|
||||
.then((response) => {
|
||||
key.value = null
|
||||
qrURL.value = null
|
||||
tokens.value = response.data.tokens
|
||||
if (response.status === 200) {
|
||||
tokens.value = response.data.tokens
|
||||
}
|
||||
successSetup.value = true
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 400) {
|
||||
@@ -218,46 +210,6 @@ async function disableTFA() {
|
||||
editTFAForm.value.reset()
|
||||
})
|
||||
}
|
||||
|
||||
async function resetRecoveryCodes() {
|
||||
const isValid = await editTFAForm.value.validate()
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
const payload = {
|
||||
password: password.value,
|
||||
}
|
||||
loadingReset.value = true
|
||||
|
||||
accountApi
|
||||
.resetRecoveryCodes(payload)
|
||||
.then((response) => {
|
||||
newTokens.value = response.data.tokens
|
||||
showCodesResetDialog.value = true
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 400) {
|
||||
passwordError.value = error.response.data.password
|
||||
} else {
|
||||
busStore.displayNotification({
|
||||
msg: $gettext(error.response.data),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loadingReset.value = false
|
||||
editTFAForm.value.reset()
|
||||
})
|
||||
}
|
||||
|
||||
function closeRecoveryCodesResetDialog() {
|
||||
newTokens.value = []
|
||||
showCodesResetDialog.value = false
|
||||
}
|
||||
if (!tfa_enabled.value) {
|
||||
getKey()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -36,10 +36,10 @@ const { $gettext } = useGettext()
|
||||
|
||||
const props = defineProps({
|
||||
// the callback should return true or false, returning false inhibits the close event
|
||||
callback_agree : {
|
||||
callback_agree: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const dialog = ref(false)
|
||||
@@ -70,7 +70,7 @@ defineExpose({
|
||||
})
|
||||
|
||||
async function agree() {
|
||||
if (props.callback_agree !== null && ! (await props.callback_agree())) {
|
||||
if (props.callback_agree !== null && !(await props.callback_agree())) {
|
||||
return
|
||||
}
|
||||
storedResolve(true)
|
||||
|
||||
@@ -46,18 +46,28 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
async function getFidoCreds() {
|
||||
return authApi.getAllFidoRegistred().then((resp) => {
|
||||
fidoCreds.value = resp.data
|
||||
if (fidoCreds.value.length > 0) {
|
||||
authUser.value.tfa_enabled = true
|
||||
authUser.value.webauthn_enabled = true
|
||||
}
|
||||
return resp
|
||||
})
|
||||
}
|
||||
|
||||
async function addFidoCred(result) {
|
||||
return authApi.endFidoRegistration(result).then(() => {
|
||||
return authApi.endFidoRegistration(result).then((res) => {
|
||||
getFidoCreds()
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteFidoCreds(id) {
|
||||
return authApi.deleteFido(id).then(() => {
|
||||
return authApi.deleteFido(id).then((res) => {
|
||||
fidoCreds.value = fidoCreds.value.filter((cred) => cred.id !== id)
|
||||
authUser.value.tfa_enabled = res.data.tfa_enabled
|
||||
if (!res.data.tfa_enabled) {
|
||||
authUser.value.webauthn_enabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
<ProfileForm />
|
||||
</v-window-item>
|
||||
<v-window-item value="security">
|
||||
<TwoFactorAuthForm />
|
||||
<TotpAuthForm />
|
||||
<BackupCodeAuthForm />
|
||||
<FidoAuthForm />
|
||||
</v-window-item>
|
||||
<v-window-item v-if="authStore.userHasMailbox" value="forward">
|
||||
@@ -47,7 +48,8 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import ProfileForm from '@/components/account/ProfileForm.vue'
|
||||
import TwoFactorAuthForm from '@/components/account/TwoFactorAuthForm.vue'
|
||||
import TotpAuthForm from '@/components/account/TotpAuthForm.vue'
|
||||
import BackupCodeAuthForm from '@/components/account/BackupCodeAuthForm.vue'
|
||||
import ForwardForm from '@/components/account/ForwardForm.vue'
|
||||
import AutoReplyForm from '@/components/account/AutoReplyForm.vue'
|
||||
import APISetupForm from '@/components/account/APISetupForm.vue'
|
||||
|
||||
@@ -23,7 +23,7 @@ class CheckTFASetupSerializer(serializers.Serializer):
|
||||
|
||||
def validate(self, data):
|
||||
user = self.context["user"]
|
||||
if user.totp_enabled or user.staticdevice_set.exists():
|
||||
if user.totp_enabled:
|
||||
raise serializers.ValidationError(_("2FA is already enabled"))
|
||||
return data
|
||||
|
||||
|
||||
@@ -63,8 +63,9 @@ class AccountViewSet(GetThrottleViewsetMixin, viewsets.ViewSet):
|
||||
# We include it as "password" to display the error
|
||||
return response.Response({"error": _("2FA is not enabled")}, status=403)
|
||||
request.user.totpdevice_set.all().delete()
|
||||
request.user.staticdevice_set.all().delete()
|
||||
request.user.totp_enabled = False
|
||||
if not request.user.tfa_enabled:
|
||||
request.user.staticdevice_set.all().delete()
|
||||
request.user.save()
|
||||
return response.Response()
|
||||
|
||||
|
||||
@@ -13,12 +13,32 @@ from modoboa.core.api.v1 import viewsets as core_v1_viewsets
|
||||
from modoboa.lib import pagination
|
||||
from modoboa.lib.throttle import GetThrottleViewsetMixin
|
||||
|
||||
import base64
|
||||
|
||||
from ... import constants
|
||||
from ... import fido2_auth as f2_auth
|
||||
from ... import models
|
||||
from . import serializers
|
||||
|
||||
|
||||
def create_static_tokens(request):
|
||||
"""Utility function to create or recreate
|
||||
static tokens if needed."""
|
||||
device = request.user.staticdevice_set.first()
|
||||
if device is None:
|
||||
device = StaticDevice.objects.create(
|
||||
user=request.user, name="{} static device".format(request.user)
|
||||
)
|
||||
elif device is not None:
|
||||
return None
|
||||
tokens = []
|
||||
for cpt in range(10):
|
||||
token = StaticToken.random_token()
|
||||
device.token_set.create(token=token)
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
class AccountViewSet(core_v1_viewsets.AccountViewSet):
|
||||
"""Account viewset."""
|
||||
|
||||
@@ -88,7 +108,7 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
|
||||
if not device:
|
||||
return response.Response(status=404)
|
||||
return response.Response(
|
||||
{"key": device.key, "url": device.config_url},
|
||||
{"key": base64.b32encode(device.bin_key), "url": device.config_url},
|
||||
content_type="application/json",
|
||||
)
|
||||
|
||||
@@ -101,28 +121,13 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
# create static device for recovery purposes
|
||||
device = StaticDevice.objects.create(
|
||||
user=request.user, name="{} static device".format(request.user)
|
||||
)
|
||||
tokens = []
|
||||
for cpt in range(10):
|
||||
token = StaticToken.random_token()
|
||||
device.token_set.create(token=token)
|
||||
tokens.append(token)
|
||||
tokens = create_static_tokens(request)
|
||||
# Set enable flag to True so we can't go here anymore
|
||||
request.user.totp_enabled = True
|
||||
request.user.save()
|
||||
# Generate new tokens
|
||||
device = request.user.totpdevice_set.first()
|
||||
# refresh = RefreshToken.for_user(request.user)
|
||||
# refresh[constants.TFA_DEVICE_TOKEN_KEY] = device.persistent_id
|
||||
return response.Response(
|
||||
{
|
||||
"tokens": tokens,
|
||||
# "refresh": str(refresh),
|
||||
# "access": str(refresh.access_token),
|
||||
}
|
||||
)
|
||||
if tokens is None:
|
||||
return response.Response(status=204)
|
||||
return response.Response({"tokens": tokens})
|
||||
|
||||
|
||||
class LogViewSet(GetThrottleViewsetMixin, viewsets.ReadOnlyModelViewSet):
|
||||
@@ -166,17 +171,23 @@ class FIDOViewSet(
|
||||
serializer_class = serializers.FIDOSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.webautn_enabled:
|
||||
if self.request.user.webauthn_enabled:
|
||||
return models.UserFidoKeys.objects.filter(user=self.request.user)
|
||||
return None
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
super().destroy(request, *args, **kwargs)
|
||||
device = request.user.totpdevice_set.first()
|
||||
if device is None:
|
||||
request.user.webautn_enabled = False
|
||||
queryset = self.get_queryset()
|
||||
to_save = False
|
||||
if not queryset.exists():
|
||||
request.user.webauthn_enabled = False
|
||||
to_save = True
|
||||
if not request.user.tfa_enabled:
|
||||
request.user.staticdevice_set.all().delete()
|
||||
to_save = True
|
||||
if to_save:
|
||||
request.user.save()
|
||||
return response.Response({"tfa_enabled": request.user.tfa_enabled})
|
||||
|
||||
@action(methods=["post"], detail=False, url_path="registration/begin")
|
||||
def registration_begin(self, request):
|
||||
@@ -197,20 +208,11 @@ class FIDOViewSet(
|
||||
credential_data=credential_data,
|
||||
user=request.user,
|
||||
)
|
||||
if not request.user.webautn_enabled:
|
||||
request.user.webautn_enabled = True
|
||||
if not request.user.webauthn_enabled:
|
||||
request.user.webauthn_enabled = True
|
||||
request.user.save()
|
||||
|
||||
# create static device for recovery purposes if needed
|
||||
device = request.user.staticdevice_set.first()
|
||||
if device is None:
|
||||
device = StaticDevice.objects.create(
|
||||
user=request.user, name="{} static device".format(request.user)
|
||||
)
|
||||
for cpt in range(10):
|
||||
token = StaticToken.random_token()
|
||||
device.token_set.create(token=token)
|
||||
return response.Response(
|
||||
{"tokens": device.token_set.all().values_list("token", flat=True)}
|
||||
)
|
||||
tokens = create_static_tokens(request)
|
||||
if tokens is not None:
|
||||
return response.Response({"tokens": tokens})
|
||||
return response.Response(status=204)
|
||||
|
||||
@@ -121,7 +121,7 @@ class LoginView(LoginViewMixin, auth_views.LoginView):
|
||||
username=user.username,
|
||||
password=form.cleaned_data["password"],
|
||||
)
|
||||
if user.totp_enabled:
|
||||
if user.tfa_enabled:
|
||||
self.request.session[constants.TFA_PRE_VERIFY_USER_PK] = user.pk
|
||||
self.request.session[constants.TFA_PRE_VERIFY_USER_BACKEND] = user.backend
|
||||
self.request.session["rememberme"] = form.cleaned_data["rememberme"]
|
||||
@@ -279,6 +279,8 @@ class TwoFactorCodeVerifyView(LoginViewMixin, generic.FormView):
|
||||
context["nextlocation"] = self.request.POST.get(
|
||||
"next", self.request.GET.get("next")
|
||||
)
|
||||
if context["nextlocation"] is None:
|
||||
context.pop("nextlocation")
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
Reference in New Issue
Block a user