Skip to content

Commit 7cb2c4b

Browse files
committed
feat(security): add security verify route
Needed for routes that require freshness, ie: /tf-setup. Requires opendatateam/udata#3644
1 parent 22f0afd commit 7cb2c4b

File tree

2 files changed

+105
-2
lines changed

2 files changed

+105
-2
lines changed

components/User/TwoFactorSetupModal.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ const fetchStatusAndQRCode = async () => {
114114
const { data: twoFactorData } = await useAPI<{ response: { tf_primary_method: string | null, reauth_required: boolean } | null }>('/tf-setup')
115115
isConfigured.value = !!twoFactorData.value?.response?.tf_primary_method
116116
if (twoFactorData.value?.response?.reauth_required) {
117-
toast.error(t('Vous devez vous déconnecter et reconnecter pour pouvoir configurer l\'authentification deux facteurs.'))
117+
toast.error(t('Vous devez confirmer votre mot de passe pour configurer l\'authentification deux facteurs.'))
118118
isOpen.value = false
119-
return
119+
await navigateTo({ path: '/verify', query: { next: '/admin/me/profile' } }, { replace: true })
120120
}
121121
122122
await fetchQRCode()

pages/verify.vue

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<template>
2+
<div class="bg-gray-some py-6 space-y-6">
3+
<div class="container bg-white max-w-xl p-6 border border-gray-lower">
4+
<h1 class="text-center text-gray-title font-extrabold text-2xl">
5+
{{ $t('Confirmer votre mot de passe') }}
6+
</h1>
7+
8+
<div class="space-y-6">
9+
<SimpleBanner
10+
v-if="getAllErrorsInErrorFields(errors, '')"
11+
type="danger"
12+
>
13+
{{ getAllErrorsInErrorFields(errors, "") }}
14+
</SimpleBanner>
15+
16+
<RequiredExplanation />
17+
18+
<form
19+
class="space-y-6"
20+
@submit.prevent="connect"
21+
>
22+
<div>
23+
<InputGroup
24+
v-model="password"
25+
type="password"
26+
:label="$t('Mot de passe')"
27+
class="w-full !mb-0"
28+
:error-text="getAllErrorsInErrorFields(errors, 'password')"
29+
:has-error="!! getAllErrorsInErrorFields(errors, 'password')"
30+
required
31+
/>
32+
</div>
33+
<div class="flex justify-center">
34+
<BrandedButton
35+
type="submit"
36+
:loading="loading"
37+
>
38+
{{ $t('Vérifier le mot de passe') }}
39+
</BrandedButton>
40+
</div>
41+
</form>
42+
</div>
43+
</div>
44+
</div>
45+
</template>
46+
47+
<script setup lang="ts">
48+
import { BrandedButton, SimpleBanner, toast } from '@datagouv/components-next'
49+
import type { FieldsErrors } from '~/types/form'
50+
import { usePostApiWithCsrf } from '~/utils/api'
51+
52+
definePageMeta({
53+
matomoIgnore: true,
54+
})
55+
56+
const { t } = useTranslation()
57+
58+
useSeoMeta({ title: t('Réauthentification'), robots: 'noindex' })
59+
60+
const password = ref('')
61+
const loading = ref(false)
62+
const errors = ref<FieldsErrors>({})
63+
const me = useMe()
64+
65+
const route = useRoute()
66+
67+
onMounted(() => {
68+
if (route.query.next) {
69+
sessionStorage.setItem('next', route.query.next as string)
70+
}
71+
})
72+
73+
const postApiWithCsrf = usePostApiWithCsrf()
74+
const connect = async () => {
75+
loading.value = true
76+
errors.value = {}
77+
78+
try {
79+
await postApiWithCsrf<{ response: { tf_required: boolean, tf_state: string } }>('/verify', {
80+
password: password.value,
81+
})
82+
83+
toast.success(t('Compte confirmé.'))
84+
await loadMe(me)
85+
86+
const next = sessionStorage.getItem('next')
87+
if (next) {
88+
navigateTo(next)
89+
}
90+
else {
91+
await navigateTo('/')
92+
}
93+
}
94+
catch (e) {
95+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
96+
const fieldsErrors = (e as any)?.response?._data?.response?.field_errors
97+
if (fieldsErrors) errors.value = fieldsErrors
98+
}
99+
finally {
100+
loading.value = false
101+
}
102+
}
103+
</script>

0 commit comments

Comments
 (0)