mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
fix(self-hosting): use instance contacts and live product name (#3333)
This commit is contained in:
1 parent
454fefdb92
commit
a5c1362f4a
308 files changed
+16533
-13512
No files matched your search
@@ -5,7 +5,7 @@
|
||||
"title": "Fluxer API",
|
||||
"version": "1.0.0",
|
||||
"description": "API for Fluxer, a free and open source instant messaging and VoIP chat app built for friends, groups, and communities.",
|
||||
"contact": {"name": "Fluxer Platform AB", "email": "support@fluxer.app"},
|
||||
"contact": {"name": "Fluxer Platform AB", "email": "support@fluxer.com"},
|
||||
"license": {"name": "AGPL-3.0", "url": "https://www.gnu.org/licenses/agpl-3.0.html"}
|
||||
},
|
||||
"servers": [{"url": "https://api.fluxer.app/v1", "description": "Production API"}],
|
||||
@@ -11362,9 +11362,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terms_url": {"nullable": true, "type": "string"},
|
||||
"privacy_url": {"nullable": true, "type": "string"}
|
||||
"privacy_url": {"nullable": true, "type": "string"},
|
||||
"guidelines_url": {"nullable": true, "type": "string"}
|
||||
},
|
||||
"required": ["terms_url", "privacy_url"],
|
||||
"required": ["terms_url", "privacy_url", "guidelines_url"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"registration": {
|
||||
@@ -11818,7 +11819,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terms_url": {"nullable": true, "type": "string", "maxLength": 2048},
|
||||
"privacy_url": {"nullable": true, "type": "string", "maxLength": 2048}
|
||||
"privacy_url": {"nullable": true, "type": "string", "maxLength": 2048},
|
||||
"guidelines_url": {"nullable": true, "type": "string", "maxLength": 2048}
|
||||
}
|
||||
},
|
||||
"registration": {
|
||||
@@ -17474,8 +17476,8 @@
|
||||
"IP authorization is required.",
|
||||
"IP authorization resend is on cooldown.",
|
||||
"IP authorization resend limit exceeded.",
|
||||
"Your IP address {ipAddress} has been permanently blocked from the Fluxer API by platform administrators. If you believe this is a mistake, contact [email protected] to appeal. Include this IP address in your appeal.",
|
||||
"Your IP address {ipAddress} has been temporarily blocked from the Fluxer API. The block lifts on its own when it expires. If you think this is a mistake, contact [email protected] and include this IP address.",
|
||||
"Your IP address {ipAddress} has been permanently blocked from the {product_name} API by platform administrators. {appealEmail, select, null {If you believe this is a mistake, contact the administrators of this instance to appeal.} other {If you believe this is a mistake, contact {appealEmail} to appeal.}} Include this IP address in your appeal.",
|
||||
"Your IP address {ipAddress} has been temporarily blocked from the {product_name} API. The block lifts on its own when it expires. {appealEmail, select, null {If you think this is a mistake, contact the administrators of this instance and include this IP address.} other {If you think this is a mistake, contact {appealEmail} and include this IP address.}}",
|
||||
"This IP address cannot be added to the blocklist.",
|
||||
"You've reached the maximum of {limit, plural, one {# application} other {# applications}}.",
|
||||
"You've reached the maximum of {count, plural, one {# bookmark} other {# bookmarks}}.",
|
||||
@@ -17543,7 +17545,7 @@
|
||||
"Test purchases cannot be applied to this account.",
|
||||
"Stream key channel mismatch.",
|
||||
"Stream key scope mismatch.",
|
||||
"Payment processing encountered an error. Please try again or contact support.",
|
||||
"{supportEmail, select, null {Payment processing encountered an error. Please try again or contact the administrators of this instance.} other {Payment processing encountered an error. Please try again or contact support.}}",
|
||||
"Gift code redemption is in progress. Try again in a moment.",
|
||||
"Invalid product selection.",
|
||||
"Invalid product configuration.",
|
||||
|
||||
@@ -417,6 +417,8 @@ pub struct AppSetupConfigResponse {
|
||||
pub struct AppLegalConfigResponse {
|
||||
pub terms_url: Option<String>,
|
||||
pub privacy_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub guidelines_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
@@ -927,6 +929,8 @@ pub struct AppLegalConfigUpdateRequest {
|
||||
pub terms_url: Option<Option<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub privacy_url: Option<Option<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub guidelines_url: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
|
||||
@@ -709,6 +709,7 @@ fn build_app_legal_update(form: &MultiValueForm) -> InstanceConfigUpdateRequest
|
||||
legal: Some(AppLegalConfigUpdateRequest {
|
||||
terms_url: optional("app_terms_url"),
|
||||
privacy_url: optional("app_privacy_url"),
|
||||
guidelines_url: optional("app_guidelines_url"),
|
||||
}),
|
||||
registration: None,
|
||||
}),
|
||||
@@ -1158,6 +1159,47 @@ pub async fn limit_config_post(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn build_app_legal_update_sends_the_guidelines_url_and_clears_blank_fields() {
|
||||
let form = MultiValueForm::parse(
|
||||
b"app_terms_url=+https%3A%2F%2Fexample.com%2Fterms+&app_privacy_url=&app_guidelines_url=https%3A%2F%2Fexample.com%2Frules",
|
||||
);
|
||||
let request = build_app_legal_update(&form);
|
||||
let legal = request
|
||||
.app_public
|
||||
.expect("app public update")
|
||||
.legal
|
||||
.expect("legal update");
|
||||
assert_eq!(
|
||||
legal.terms_url,
|
||||
Some(Some("https://example.com/terms".to_owned()))
|
||||
);
|
||||
assert_eq!(legal.privacy_url, Some(None));
|
||||
assert_eq!(
|
||||
legal.guidelines_url,
|
||||
Some(Some("https://example.com/rules".to_owned()))
|
||||
);
|
||||
let body = serde_json::to_value(&legal).expect("serialize legal update");
|
||||
assert_eq!(
|
||||
body,
|
||||
serde_json::json!({
|
||||
"terms_url": "https://example.com/terms",
|
||||
"privacy_url": null,
|
||||
"guidelines_url": "https://example.com/rules"
|
||||
})
|
||||
);
|
||||
|
||||
let cleared = build_app_legal_update(&MultiValueForm::parse(
|
||||
b"app_terms_url=&app_privacy_url=&app_guidelines_url=+",
|
||||
));
|
||||
let body = serde_json::to_value(cleared.app_public.expect("app public").legal)
|
||||
.expect("serialize cleared legal update");
|
||||
assert_eq!(
|
||||
body,
|
||||
serde_json::json!({"terms_url": null, "privacy_url": null, "guidelines_url": null})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_integrations_update_leaves_email_alone_when_its_fields_are_hidden() {
|
||||
let hidden = build_integrations_update(&MultiValueForm::parse(
|
||||
|
||||
@@ -167,6 +167,7 @@ pub fn instance_config_page(
|
||||
csrf_token,
|
||||
&instance_config.integrations,
|
||||
instance_config.account_identity.mode,
|
||||
&instance_config.app_public.branding.product_name,
|
||||
))
|
||||
},
|
||||
))
|
||||
@@ -568,6 +569,7 @@ fn integrations_config_section(
|
||||
csrf_token: &str,
|
||||
integrations: &InstanceIntegrationsResponse,
|
||||
account_identity: AccountIdentityMode,
|
||||
product_name: &str,
|
||||
) -> Markup {
|
||||
let smtp_port = integrations
|
||||
.email
|
||||
@@ -629,7 +631,7 @@ fn integrations_config_section(
|
||||
"integration_email_from_name",
|
||||
"From name",
|
||||
integrations.email.from_name.as_deref().unwrap_or(""),
|
||||
"Fluxer",
|
||||
product_name,
|
||||
))
|
||||
(text_input(
|
||||
"integration_smtp_host",
|
||||
@@ -955,9 +957,18 @@ fn app_public_config_section(
|
||||
app_public.legal.privacy_url.as_deref().unwrap_or(""),
|
||||
"https://example.com/privacy",
|
||||
))
|
||||
(text_input(
|
||||
"app_guidelines_url",
|
||||
"Community Guidelines URL",
|
||||
app_public.legal.guidelines_url.as_deref().unwrap_or(""),
|
||||
"https://example.com/guidelines",
|
||||
))
|
||||
}
|
||||
p class="text-xs text-neutral-500" {
|
||||
"Registration asks for agreement only to the documents configured here. Leave both blank to hide legal consent on self-hosted registration."
|
||||
"Registration asks for agreement only to the terms and privacy documents configured here. Leave both blank to hide legal consent on self-hosted registration."
|
||||
}
|
||||
p class="text-xs text-neutral-500" {
|
||||
"The community guidelines URL is linked from the app, the report flow and enforcement emails. Use an absolute http or https URL. Leave it blank to hide those links."
|
||||
}
|
||||
(form_actions(html! {
|
||||
(submit_button("Save Legal Documents"))
|
||||
@@ -1966,6 +1977,7 @@ mod tests {
|
||||
"csrf",
|
||||
&integrations,
|
||||
AccountIdentityMode::Username,
|
||||
"Fluxer",
|
||||
)
|
||||
.into_string();
|
||||
assert!(!username.contains("Email delivery"));
|
||||
@@ -1978,6 +1990,7 @@ mod tests {
|
||||
"csrf",
|
||||
&integrations,
|
||||
AccountIdentityMode::Email,
|
||||
"Fluxer",
|
||||
)
|
||||
.into_string();
|
||||
assert!(email.contains("Email delivery"));
|
||||
|
||||
@@ -17,7 +17,7 @@ fn deserialize_admin_users_me_response() {
|
||||
"premium_flags": 2,
|
||||
"avatar": "563de430",
|
||||
"banner": "bd221c57",
|
||||
"bio": "For support, please contact support@fluxer.app.",
|
||||
"bio": "For support, please contact support@fluxer.com.",
|
||||
"pronouns": "he/him",
|
||||
"accent_color": 2631308,
|
||||
"email": "[email protected]",
|
||||
@@ -1327,6 +1327,51 @@ fn deserialize_report_legal_hold_fields_and_hold_response() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn instance_legal_config_carries_the_guidelines_url() {
|
||||
let with: types::AppLegalConfigResponse = serde_json::from_value(serde_json::json!({
|
||||
"terms_url": "https://example.com/terms",
|
||||
"privacy_url": null,
|
||||
"guidelines_url": "https://example.com/rules"
|
||||
}))
|
||||
.expect("legal config with guidelines");
|
||||
assert_eq!(
|
||||
with.guidelines_url.as_deref(),
|
||||
Some("https://example.com/rules")
|
||||
);
|
||||
let generated: generated_types::InstanceConfigResponseAppPublicLegal =
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"terms_url": null,
|
||||
"privacy_url": null,
|
||||
"guidelines_url": "https://example.com/rules"
|
||||
}))
|
||||
.expect("generated legal config");
|
||||
assert_eq!(
|
||||
generated.guidelines_url.as_deref(),
|
||||
Some("https://example.com/rules")
|
||||
);
|
||||
let old: types::AppLegalConfigResponse = serde_json::from_value(serde_json::json!({
|
||||
"terms_url": "https://example.com/terms",
|
||||
"privacy_url": "https://example.com/privacy"
|
||||
}))
|
||||
.expect("legal config from an older api");
|
||||
assert!(old.guidelines_url.is_none());
|
||||
|
||||
let update = types::AppLegalConfigUpdateRequest {
|
||||
terms_url: Some(None),
|
||||
privacy_url: None,
|
||||
guidelines_url: Some(Some("https://example.com/rules".to_owned())),
|
||||
};
|
||||
let body = serde_json::to_value(&update).expect("serialize legal update");
|
||||
assert_eq!(
|
||||
body,
|
||||
serde_json::json!({"terms_url": null, "guidelines_url": "https://example.com/rules"})
|
||||
);
|
||||
let accepted: generated_types::InstanceConfigUpdateRequestAppPublicLegal =
|
||||
serde_json::from_value(body).expect("the update matches the generated request contract");
|
||||
assert!(accepted.guidelines_url.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_webhook_report_with_a_bot_creator_and_a_deleted_creator() {
|
||||
let bot = decode_report_both_ways(report_json(serde_json::json!({
|
||||
|
||||
@@ -812,6 +812,90 @@ async fn report_legal_hold_form_places_and_clears_the_hold() {
|
||||
assert_eq!(received.lock().unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn instance_config_legal_form_round_trips_the_guidelines_url() {
|
||||
let patches = Arc::new(Mutex::new(Vec::<Value>::new()));
|
||||
let sink = patches.clone();
|
||||
let configured = || {
|
||||
let mut config = instance_config();
|
||||
config["self_hosted"] = json!(true);
|
||||
config["app_public"] = json!({
|
||||
"branding": {"product_name": "Fluxer", "premium_product_name": "Premium"},
|
||||
"setup": {"configured": true},
|
||||
"legal": {
|
||||
"terms_url": "https://example.com/terms",
|
||||
"privacy_url": null,
|
||||
"guidelines_url": "https://example.com/community-guidelines"
|
||||
},
|
||||
"registration": {"collect_date_of_birth": true}
|
||||
});
|
||||
config
|
||||
};
|
||||
let api = Router::new()
|
||||
.route(
|
||||
"/admin/instance/config",
|
||||
routing::get(move || async move { json_response(configured()) }).patch(
|
||||
move |Json(body): Json<Value>| {
|
||||
let sink = sink.clone();
|
||||
async move {
|
||||
sink.lock().unwrap().push(body);
|
||||
json_response(configured())
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
.fallback(mock_api);
|
||||
let app = setup_with_api(api).await;
|
||||
let (headers, page) = get_with_headers(&app, "/instance-config", &[]).await;
|
||||
assert!(
|
||||
page.contains(r#"id="app_guidelines_url" name="app_guidelines_url" value="https://example.com/community-guidelines""#),
|
||||
"{page}"
|
||||
);
|
||||
assert!(page.contains("Community Guidelines URL"), "{page}");
|
||||
let csrf_token = csrf_cookie(&headers)
|
||||
.unwrap_or_else(|| panic!("instance config page did not set csrf_token cookie\n{page}"));
|
||||
assert_form_has_csrf(
|
||||
&page,
|
||||
"/instance-config?action=update_app_legal",
|
||||
&csrf_token,
|
||||
);
|
||||
let cookie = format!("{}; csrf_token={}", app.session_cookie, csrf_token);
|
||||
for form in [
|
||||
format!(
|
||||
"_csrf={csrf_token}&app_terms_url=https%3A%2F%2Fexample.com%2Fterms&app_privacy_url=&app_guidelines_url=https%3A%2F%2Frules.example.org%2Fguidelines"
|
||||
),
|
||||
format!("_csrf={csrf_token}&app_terms_url=&app_privacy_url=&app_guidelines_url="),
|
||||
] {
|
||||
let (status, _, body) = post_form_with_headers(
|
||||
&app,
|
||||
"/instance-config?action=update_app_legal",
|
||||
&[("Cookie", &cookie)],
|
||||
&form,
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
status.is_redirection() || status.is_success(),
|
||||
"{status} {body}"
|
||||
);
|
||||
}
|
||||
let patches = patches.lock().unwrap();
|
||||
assert_eq!(
|
||||
*patches,
|
||||
vec![
|
||||
json!({"app_public": {"legal": {
|
||||
"terms_url": "https://example.com/terms",
|
||||
"privacy_url": null,
|
||||
"guidelines_url": "https://rules.example.org/guidelines"
|
||||
}}}),
|
||||
json!({"app_public": {"legal": {
|
||||
"terms_url": null,
|
||||
"privacy_url": null,
|
||||
"guidelines_url": null
|
||||
}}}),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_fragment_alias_returns_drawer_fragment() {
|
||||
let app = setup().await;
|
||||
|
||||
Reference in new issue
Block a user