Skip to main content

mas_templates/context/
captcha.rs

1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use std::{collections::BTreeMap, sync::Arc};
9
10use mas_i18n::DataLocale;
11use minijinja::{
12    Value,
13    value::{Enumerator, Object},
14};
15use rand::Rng;
16use serde::Serialize;
17
18use crate::{TemplateContext, context::SampleIdentifier};
19
20#[derive(Debug)]
21struct CaptchaConfig(mas_data_model::CaptchaConfig);
22
23impl Object for CaptchaConfig {
24    fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
25        match key.as_str() {
26            Some("service") => Some(match &self.0.service {
27                mas_data_model::CaptchaService::RecaptchaV2 => "recaptcha_v2".into(),
28                mas_data_model::CaptchaService::CloudflareTurnstile => {
29                    "cloudflare_turnstile".into()
30                }
31                mas_data_model::CaptchaService::HCaptcha => "hcaptcha".into(),
32            }),
33            Some("site_key") => Some(self.0.site_key.clone().into()),
34            _ => None,
35        }
36    }
37
38    fn enumerate(self: &Arc<Self>) -> Enumerator {
39        Enumerator::Str(&["service", "site_key"])
40    }
41}
42
43/// Context with an optional CAPTCHA configuration in it
44#[derive(Serialize)]
45pub struct WithCaptcha<T> {
46    captcha_config: Option<Value>,
47
48    #[serde(flatten)]
49    inner: T,
50}
51
52impl<T> WithCaptcha<T> {
53    #[must_use]
54    pub(crate) fn new(captcha: Option<mas_data_model::CaptchaConfig>, inner: T) -> Self {
55        Self {
56            captcha_config: captcha.map(|captcha| Value::from_object(CaptchaConfig(captcha))),
57            inner,
58        }
59    }
60}
61
62impl<T: TemplateContext> TemplateContext for WithCaptcha<T> {
63    fn sample<R: Rng>(
64        now: chrono::DateTime<chrono::prelude::Utc>,
65        rng: &mut R,
66        locales: &[DataLocale],
67    ) -> BTreeMap<SampleIdentifier, Self>
68    where
69        Self: Sized,
70    {
71        T::sample(now, rng, locales)
72            .into_iter()
73            .map(|(k, inner)| (k, Self::new(None, inner)))
74            .collect()
75    }
76}