1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![allow(clippy::str_to_string)] // ComplexObject macro uses &str.to_string()

use async_graphql::{ComplexObject, Enum, SimpleObject, ID};
use url::Url;

pub const SITE_CONFIG_ID: &str = "site_config";
pub const CAPTCHA_CONFIG_ID: &str = "captcha_config";

#[derive(SimpleObject)]
#[graphql(complex)]
#[allow(clippy::struct_excessive_bools)]
pub struct SiteConfig {
    /// The configuration of CAPTCHA provider.
    captcha_config: Option<CaptchaConfig>,

    /// The server name of the homeserver.
    server_name: String,

    /// The URL to the privacy policy.
    policy_uri: Option<Url>,

    /// The URL to the terms of service.
    tos_uri: Option<Url>,

    /// Imprint to show in the footer.
    imprint: Option<String>,

    /// Whether users can change their email.
    email_change_allowed: bool,

    /// Whether users can change their display name.
    display_name_change_allowed: bool,

    /// Whether passwords are enabled for login.
    password_login_enabled: bool,

    /// Whether passwords are enabled and users can change their own passwords.
    password_change_allowed: bool,

    /// Whether passwords are enabled and users can register using a password.
    password_registration_enabled: bool,

    /// Minimum password complexity, from 0 to 4, in terms of a zxcvbn score.
    /// The exact scorer (including dictionaries and other data tables)
    /// in use is <https://crates.io/crates/zxcvbn>.
    minimum_password_complexity: u8,
}

#[derive(SimpleObject)]
#[graphql(complex)]
pub struct CaptchaConfig {
    /// Which Captcha service is being used
    pub service: CaptchaService,

    /// The site key used by the instance
    pub site_key: String,
}

/// Which Captcha service is being used
#[derive(Enum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum CaptchaService {
    RecaptchaV2,
    CloudflareTurnstile,
    HCaptcha,
}

#[ComplexObject]
impl SiteConfig {
    /// The ID of the site configuration.
    pub async fn id(&self) -> ID {
        SITE_CONFIG_ID.into()
    }
}

impl SiteConfig {
    /// Create a new [`SiteConfig`] from the data model
    /// [`mas_data_model:::SiteConfig`].
    pub fn new(data_model: &mas_data_model::SiteConfig) -> Self {
        Self {
            captcha_config: data_model.captcha.as_ref().map(CaptchaConfig::new),
            server_name: data_model.server_name.clone(),
            policy_uri: data_model.policy_uri.clone(),
            tos_uri: data_model.tos_uri.clone(),
            imprint: data_model.imprint.clone(),
            email_change_allowed: data_model.email_change_allowed,
            display_name_change_allowed: data_model.displayname_change_allowed,
            password_login_enabled: data_model.password_login_enabled,
            password_change_allowed: data_model.password_change_allowed,
            password_registration_enabled: data_model.password_registration_enabled,
            minimum_password_complexity: data_model.minimum_password_complexity,
        }
    }
}

#[ComplexObject]
impl CaptchaConfig {
    pub async fn id(&self) -> ID {
        CAPTCHA_CONFIG_ID.into()
    }
}

impl CaptchaConfig {
    /// Create a new [`CaptchaConfig`] from the data model
    /// [`mas_data_model:::CaptchaConfig`].
    pub fn new(data_model: &mas_data_model::CaptchaConfig) -> Self {
        Self {
            service: match data_model.service {
                mas_data_model::CaptchaService::RecaptchaV2 => CaptchaService::RecaptchaV2,
                mas_data_model::CaptchaService::CloudflareTurnstile => {
                    CaptchaService::CloudflareTurnstile
                }
                mas_data_model::CaptchaService::HCaptcha => CaptchaService::HCaptcha,
            },
            site_key: data_model.site_key.clone(),
        }
    }
}