@dargmuesli/nuxt-vio 20.2.0 → 20.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <VioForm :form="v$" :is-form-sent="isFormSent" @submit="submit">
2
+ <VioForm
3
+ :form="v$"
4
+ :is-loading="isLoading"
5
+ :is-form-sent="isFormSent"
6
+ @submit.prevent="submit"
7
+ >
3
8
  <input type="hidden" name="static-form-name" value="contact" />
4
9
  <VioFormInput
5
10
  id-label="input-name"
@@ -61,6 +66,10 @@
61
66
  </VioFormInputStateError>
62
67
  </template>
63
68
  </VioFormInput>
69
+ <VioFormInputCaptcha
70
+ :form-input="v$.captcha"
71
+ @input="form.captcha = $event"
72
+ />
64
73
  </VioForm>
65
74
  </template>
66
75
 
@@ -68,12 +77,28 @@
68
77
  import { useVuelidate } from '@vuelidate/core'
69
78
  import { required } from '@vuelidate/validators'
70
79
 
80
+ type FormValid = { emailAddress: string; name: string; message: string }
81
+
82
+ withDefaults(
83
+ defineProps<{
84
+ isLoading?: boolean
85
+ }>(),
86
+ {
87
+ isLoading: undefined,
88
+ },
89
+ )
90
+
91
+ const emit = defineEmits<{
92
+ submit: [form: FormValid]
93
+ }>()
94
+
71
95
  const { t } = useI18n()
72
96
  const runtimeConfig = useRuntimeConfig()
73
97
  const siteConfig = useSiteConfig()
74
98
 
75
99
  // data
76
100
  const form = reactive({
101
+ captcha: ref<string>(),
77
102
  emailAddress: ref<string>(),
78
103
  name: ref<string>(),
79
104
  message: ref<string>(),
@@ -81,11 +106,16 @@ const form = reactive({
81
106
  const isFormSent = ref(false)
82
107
 
83
108
  // methods
84
- const submit = async (e: Event) =>
85
- !(await isFormValid({ v$, isFormSent })) ? e.preventDefault() : undefined
109
+ const submit = async () => {
110
+ if (!(await isFormValid({ v$, isFormSent }))) return
111
+ emit('submit', form as FormValid)
112
+ }
86
113
 
87
114
  // vuelidate
88
115
  const rules = {
116
+ captcha: {
117
+ required,
118
+ },
89
119
  emailAddress: {
90
120
  // maxLength: maxLength(100),
91
121
  required,
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <VioFormInput
3
+ :class="{
4
+ hidden: !isVisible && !formInput.$error,
5
+ }"
6
+ :title="t('captcha')"
7
+ :value="formInput"
8
+ >
9
+ <NuxtTurnstile
10
+ ref="turnstileRef"
11
+ :class="{ 'h-16.25': isVisible }"
12
+ :options="{
13
+ 'error-callback': () => (isLoading = false),
14
+ 'expired-callback': () => emit('input', undefined),
15
+ }"
16
+ @update:model-value="update"
17
+ />
18
+ <VioFormInputStateError
19
+ v-if="!isVisible"
20
+ :form-input="formInput"
21
+ validation-property="required"
22
+ >
23
+ {{ t('globalValidationRequired') }}
24
+ </VioFormInputStateError>
25
+ <template v-if="isVisible" #stateError>
26
+ <VioFormInputStateError
27
+ :form-input="formInput"
28
+ validation-property="required"
29
+ >
30
+ {{ t('globalValidationRequired') }}
31
+ </VioFormInputStateError>
32
+ </template>
33
+ <template v-if="!isVisible && isLoading" #stateInfo>
34
+ <VioFormInputStateInfo>
35
+ {{ t('globalStatusLoading') }}
36
+ </VioFormInputStateInfo>
37
+ </template>
38
+ <template v-if="formInput.$error" #assistance>
39
+ <VioButtonColored :aria-label="t('reset')" @click="reset">
40
+ {{ t('reset') }}
41
+ <template #prefix>
42
+ <!-- <IHeroiconsArrowPath /> -->
43
+ </template>
44
+ </VioButtonColored>
45
+ </template>
46
+ </VioFormInput>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import type { BaseValidation } from '@vuelidate/core'
51
+
52
+ withDefaults(
53
+ defineProps<{
54
+ formInput: BaseValidation
55
+ }>(),
56
+ {},
57
+ )
58
+
59
+ const emit = defineEmits<{
60
+ input: [event?: string]
61
+ }>()
62
+
63
+ const { t } = useI18n()
64
+ const runtimeConfig = useRuntimeConfig()
65
+
66
+ // refs
67
+ const turnstileRef = ref()
68
+
69
+ // data
70
+ const isLoading = ref(true)
71
+
72
+ // computations
73
+ const isVisible = computed(
74
+ () => !runtimeConfig.public.vio.isTesting,
75
+ // TODO: implement invisible widget type with fallback in case of required user interaction (https://github.com/maevsi/maevsi/issues/1239)
76
+ // !['1x00000000000000000000BB', '2x00000000000000000000BB'].includes(
77
+ // config.public.turnstile.siteKey
78
+ // )
79
+ )
80
+
81
+ // methods
82
+ const reset = () => {
83
+ isLoading.value = true
84
+ turnstileRef.value.reset()
85
+ }
86
+ const update = (e: string) => {
87
+ isLoading.value = false
88
+ emit('input', e)
89
+ }
90
+ </script>
91
+
92
+ <i18n lang="yaml">
93
+ de:
94
+ captcha: Captcha
95
+ reset: Captcha neu laden
96
+ en:
97
+ captcha: Captcha
98
+ reset: Reload captcha
99
+ </i18n>
package/nuxt.config.ts CHANGED
@@ -54,6 +54,7 @@ export default defineNuxtConfig(
54
54
  '@nuxtjs/html-validator',
55
55
  '@nuxtjs/i18n',
56
56
  '@nuxtjs/seo',
57
+ '@nuxtjs/turnstile',
57
58
  '@pinia/nuxt',
58
59
  'nuxt-gtag',
59
60
  'shadcn-nuxt',
package/package.json CHANGED
@@ -3,6 +3,7 @@
3
3
  "@dargmuesli/nuxt-cookie-control": "9.1.13",
4
4
  "@eslint/compat": "2.0.1",
5
5
  "@heroicons/vue": "2.2.0",
6
+ "@nuxtjs/turnstile": "1.1.1",
6
7
  "@http-util/status-i18n": "0.9.0",
7
8
  "@intlify/eslint-plugin-vue-i18n": "4.1.1",
8
9
  "@nuxt/devtools": "3.1.1",
@@ -114,5 +115,5 @@
114
115
  "start:static": "serve playground/.output/public --ssl-cert ./.config/certificates/ssl.crt --ssl-key ./.config/certificates/ssl.key"
115
116
  },
116
117
  "type": "module",
117
- "version": "20.2.0"
118
+ "version": "20.4.0"
118
119
  }
@@ -0,0 +1,25 @@
1
+ import { consola } from 'consola'
2
+
3
+ /**
4
+ * Verifies a Turnstile token from the request body
5
+ * @throws {Error} If token is missing or verification fails
6
+ */
7
+ export const assertTurnstileValid = async ({ token }: { token?: string }) => {
8
+ if (!token) {
9
+ throw createError({
10
+ statusCode: 422,
11
+ statusMessage: 'Turnstile token not provided.',
12
+ })
13
+ }
14
+
15
+ const result = await verifyTurnstileToken(token)
16
+
17
+ if (!result.success) {
18
+ throw createError({
19
+ statusCode: 403,
20
+ statusMessage: `Turnstile verification unsuccessful: ${result['error-codes'].join(', ')}`,
21
+ })
22
+ }
23
+
24
+ consola.debug('Turnstile verification succeeded')
25
+ }
@@ -45,6 +45,11 @@ export const VIO_GET_CSP = ({ siteUrl }: { siteUrl: URL }) =>
45
45
  // Google Service Worker (https://developers.google.com/tag-platform/tag-manager/web/csp)
46
46
  'frame-src': ['https://www.googletagmanager.com'],
47
47
  },
48
+ {
49
+ // Cloudflare Turnstile
50
+ 'frame-src': ['https://challenges.cloudflare.com'],
51
+ 'script-src-elem': ['https://challenges.cloudflare.com'],
52
+ },
48
53
  {
49
54
  // vio
50
55
  'manifest-src': [`${siteUrl}site.webmanifest`],