mas_templates/context/
captcha.rs

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