From 3e4899c8e4e6e2056f4d206ab104ae36fd888b85 Mon Sep 17 00:00:00 2001 From: Laurent GAY Date: Wed, 3 Sep 2025 21:07:33 +0200 Subject: [PATCH] [Adminitration] Allow registrations: 3 modes = "close", "allowed", "moderate" - #877 --- config/config.exs | 1 + config/dev.exs | 2 + config/docker.exs | 2 + config/e2e.exs | 1 + config/test.exs | 1 + lib/graphql/resolvers/config.ex | 1 + lib/graphql/schema/admin.ex | 10 + lib/graphql/schema/config.ex | 4 + lib/mobilizon/config.ex | 13 ++ lib/web/controllers/node_info_controller.ex | 1 + schema.graphql | 9 + src/composition/apollo/config.ts | 9 +- src/graphql/admin.ts | 3 + src/graphql/config.ts | 2 + src/i18n/en_US.json | 3 +- src/types/admin.model.ts | 1 + src/types/config.model.ts | 1 + src/types/enums.ts | 6 + src/views/Admin/SettingsView.vue | 74 ++++++-- src/views/HomeView.vue | 6 +- src/views/User/LoginView.vue | 5 +- test/graphql/resolvers/admin_test.exs | 62 +++++- test/graphql/resolvers/config_test.exs | 9 + test/graphql/resolvers/user_test.exs | 11 ++ .../controllers/nodeinfo_controller_test.exs | 1 + .../admin/__snapshots__/settings.spec.ts.snap | 129 +++++++++++++ .../specs/components/admin/settings.spec.ts | 176 ++++++++++++++++++ tests/unit/specs/mocks/config.ts | 2 + 28 files changed, 526 insertions(+), 19 deletions(-) create mode 100644 tests/unit/specs/components/admin/__snapshots__/settings.spec.ts.snap create mode 100644 tests/unit/specs/components/admin/settings.spec.ts diff --git a/config/config.exs b/config/config.exs index ef195cd22..859166533 100644 --- a/config/config.exs +++ b/config/config.exs @@ -17,6 +17,7 @@ config :mobilizon, :instance, description: "Change this to a proper description of your instance", hostname: "localhost", registrations_open: false, + registrations_moderation: false, registration_email_allowlist: [], registration_email_denylist: [], disable_database_login: false, diff --git a/config/dev.exs b/config/dev.exs index a12c110f0..63cb90f22 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -82,6 +82,8 @@ config :mobilizon, :instance, email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL"), email_reply_to: System.get_env("MOBILIZON_INSTANCE_EMAIL"), registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN") == "true", + registrations_moderation: + System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_MODERATION", "false") == "true", groups: true config :mobilizon, Mobilizon.Web.Auth.Guardian, diff --git a/config/docker.exs b/config/docker.exs index 3ae67b79a..ea4295369 100644 --- a/config/docker.exs +++ b/config/docker.exs @@ -54,6 +54,8 @@ config :mobilizon, :instance, ), hostname: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan"), registrations_open: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_OPEN", "false") == "true", + registrations_moderation: + System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_MODERATION", "false") == "true", registration_email_allowlist: System.get_env("MOBILIZON_INSTANCE_REGISTRATIONS_EMAIL_ALLOWLIST", "") |> String.split(",", trim: true), diff --git a/config/e2e.exs b/config/e2e.exs index 5ee1b8c1c..6ec137718 100644 --- a/config/e2e.exs +++ b/config/e2e.exs @@ -34,6 +34,7 @@ config :mobilizon, :instance, description: "E2E is safety", hostname: "mobilizon1.com", registrations_open: true, + registrations_moderation: false, registration_email_denylist: ["gmail.com", "deny@tcit.fr"], demo: false, default_language: "en", diff --git a/config/test.exs b/config/test.exs index 01dc74134..61e0045f7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -3,6 +3,7 @@ import Config config :mobilizon, :instance, name: "Test instance", registrations_open: true, + registrations_moderation: false, duration_of_long_event: 0 # We don't run a server during test. If one is required, diff --git a/lib/graphql/resolvers/config.ex b/lib/graphql/resolvers/config.ex index 57ffe14dc..162954b76 100644 --- a/lib/graphql/resolvers/config.ex +++ b/lib/graphql/resolvers/config.ex @@ -90,6 +90,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do %{ name: Config.instance_name(), registrations_open: Config.instance_registrations_open?(), + registrations_moderation: Config.instance_registrations_moderation?(), registrations_allowlist: Config.instance_registrations_allowlist?(), contact: Config.contact(), demo_mode: Config.instance_demo_mode?(), diff --git a/lib/graphql/schema/admin.ex b/lib/graphql/schema/admin.ex index c5f2beb9a..97b2eada0 100644 --- a/lib/graphql/schema/admin.ex +++ b/lib/graphql/schema/admin.ex @@ -153,6 +153,11 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do field(:instance_privacy_policy_url, :string, description: "The instance's privacy policy URL") field(:instance_rules, :string, description: "The instance's rules") field(:registrations_open, :boolean, description: "Whether the registrations are opened") + + field(:registrations_moderation, :boolean, + description: "Whether the registrations want moderation" + ) + field(:instance_languages, list_of(:string), description: "The instance's languages") end @@ -464,6 +469,11 @@ defmodule Mobilizon.GraphQL.Schema.AdminType do arg(:instance_privacy_policy_url, :string, description: "The instance's privacy policy URL") arg(:instance_rules, :string, description: "The instance's rules") arg(:registrations_open, :boolean, description: "Whether the registrations are opened") + + arg(:registrations_moderation, :boolean, + description: "Whether the registrations want moderation" + ) + arg(:instance_languages, list_of(:string), description: "The instance's languages") middleware(Rajska.QueryAuthorization, permit: :administrator) resolve(&Admin.save_settings/3) diff --git a/lib/graphql/schema/config.ex b/lib/graphql/schema/config.ex index 9107b2062..751c02c50 100644 --- a/lib/graphql/schema/config.ex +++ b/lib/graphql/schema/config.ex @@ -26,6 +26,10 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do field(:registrations_open, :boolean, description: "Whether the registrations are opened") + field(:registrations_moderation, :boolean, + description: "Whether the registrations want moderation" + ) + field(:registrations_allowlist, :boolean, description: "Whether the registration are on an allowlist" ) diff --git a/lib/mobilizon/config.ex b/lib/mobilizon/config.ex index 87039e365..c6ad94d4b 100644 --- a/lib/mobilizon/config.ex +++ b/lib/mobilizon/config.ex @@ -15,6 +15,7 @@ defmodule Mobilizon.Config do description: String.t(), hostname: String.t(), registrations_open: boolean(), + registrations_moderation: boolean(), languages: list(String.t()), default_language: String.t(), registration_email_allowlist: list(String.t()), @@ -154,6 +155,17 @@ defmodule Mobilizon.Config do ) ) + @spec instance_registrations_moderation? :: boolean + def instance_registrations_moderation?, + do: + to_boolean( + config_cached_value( + "instance", + "registrations_moderation", + instance_config()[:registrations_moderation] + ) + ) + @spec instance_languages :: list(String.t()) def instance_languages, do: @@ -451,6 +463,7 @@ defmodule Mobilizon.Config do instance_name: instance_name(), instance_slogan: instance_slogan(), registrations_open: instance_registrations_open?(), + registrations_moderation: instance_registrations_moderation?(), contact: contact(), primary_color: primary_color(), secondary_color: secondary_color(), diff --git a/lib/web/controllers/node_info_controller.ex b/lib/web/controllers/node_info_controller.ex index 71e7b79aa..48426a2a6 100644 --- a/lib/web/controllers/node_info_controller.ex +++ b/lib/web/controllers/node_info_controller.ex @@ -53,6 +53,7 @@ defmodule Mobilizon.Web.NodeInfoController do outbound: ["atom1.0"] }, openRegistrations: Config.instance_registrations_open?(), + moderationRegistrations: Config.instance_registrations_moderation?(), usage: %{ users: %{ total: Statistics.get_cached_value(:local_users) diff --git a/schema.graphql b/schema.graphql index 43d06390a..c59b1bd1d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -193,6 +193,9 @@ type Config { "Whether the registrations are opened" registrationsOpen: Boolean + "Whether the registrations want moderation" + registrationsModeration: Boolean + "Whether the registration are on an allowlist" registrationsAllowlist: Boolean @@ -1929,6 +1932,9 @@ type RootMutationType { "Whether the registrations are opened" registrationsOpen: Boolean + "Whether the registrations want moderation" + registrationsModeration: Boolean + "The instance's languages" instanceLanguages: [String] ): AdminSettings @@ -3761,6 +3767,9 @@ type AdminSettings { "Whether the registrations are opened" registrationsOpen: Boolean + "Whether the registrations want moderation" + registrationsModeration: Boolean + "The instance's languages" instanceLanguages: [String] } diff --git a/src/composition/apollo/config.ts b/src/composition/apollo/config.ts index 702a291ae..434d9cf65 100644 --- a/src/composition/apollo/config.ts +++ b/src/composition/apollo/config.ts @@ -229,13 +229,19 @@ export function useRegistrationConfig() { const { result, error, loading, onResult } = useQuery<{ config: Pick< IConfig, - "registrationsOpen" | "registrationsAllowlist" | "auth" + | "registrationsOpen" + | "registrationsModeration" + | "registrationsAllowlist" + | "auth" >; }>(CONFIG); const registrationsOpen = computed( () => result.value?.config?.registrationsOpen ); + const registrationsModeration = computed( + () => result.value?.config?.registrationsModeration + ); const registrationsAllowlist = computed( () => result.value?.config?.registrationsAllowlist ); @@ -244,6 +250,7 @@ export function useRegistrationConfig() { ); return { registrationsOpen, + registrationsModeration, registrationsAllowlist, databaseLogin, error, diff --git a/src/graphql/admin.ts b/src/graphql/admin.ts index 884b91fa3..86d5c4381 100644 --- a/src/graphql/admin.ts +++ b/src/graphql/admin.ts @@ -225,6 +225,7 @@ export const ADMIN_SETTINGS_FRAGMENT = gql` instancePrivacyPolicyUrl instanceRules registrationsOpen + registrationsModeration instanceLanguages } `; @@ -258,6 +259,7 @@ export const SAVE_ADMIN_SETTINGS = gql` $instancePrivacyPolicyUrl: String $instanceRules: String $registrationsOpen: Boolean + $registrationsModeration: Boolean $instanceLanguages: [String] ) { saveAdminSettings( @@ -279,6 +281,7 @@ export const SAVE_ADMIN_SETTINGS = gql` instancePrivacyPolicyUrl: $instancePrivacyPolicyUrl instanceRules: $instanceRules registrationsOpen: $registrationsOpen + registrationsModeration: $registrationsModeration instanceLanguages: $instanceLanguages ) { ...adminSettingsFragment diff --git a/src/graphql/config.ts b/src/graphql/config.ts index 4fa84932e..929219805 100644 --- a/src/graphql/config.ts +++ b/src/graphql/config.ts @@ -11,6 +11,7 @@ export const CONFIG = gql` version federating registrationsOpen + registrationsModeration registrationsAllowlist demoMode longEvents @@ -205,6 +206,7 @@ export const ABOUT = gql` contact languages registrationsOpen + registrationsModeration registrationsAllowlist anonymous { participation { diff --git a/src/i18n/en_US.json b/src/i18n/en_US.json index 9401421ce..f6ae1ba5f 100644 --- a/src/i18n/en_US.json +++ b/src/i18n/en_US.json @@ -975,6 +975,7 @@ "Registration is allowed, anyone can register.": "Registration is allowed, anyone can register.", "Registration is closed.": "Registration is closed.", "Registration is currently closed.": "Registration is currently closed.", + "Registration is moderated, new user must be validated.": "Registration is moderated, new user must be validated.", "Registrations": "Registrations", "Registrations are restricted by allowlisting.": "Registrations are restricted by allowlisting.", "Reject": "Reject", @@ -1698,4 +1699,4 @@ "{user}'s follow request was accepted": "{user}'s follow request was accepted", "{user}'s follow request was rejected": "{user}'s follow request was rejected", "\u00a9 The OpenStreetMap Contributors": "\u00a9 The OpenStreetMap Contributors" -} \ No newline at end of file +} diff --git a/src/types/admin.model.ts b/src/types/admin.model.ts index 4147ac79b..de08b7ee9 100644 --- a/src/types/admin.model.ts +++ b/src/types/admin.model.ts @@ -39,5 +39,6 @@ export interface IAdminSettings { instancePrivacyPolicyUrl: string | null; instanceRules: string; registrationsOpen: boolean; + registrationsModeration: boolean; instanceLanguages: string[]; } diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 4a29c1340..207516ec3 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -43,6 +43,7 @@ export interface IConfig { secondaryColor: string; registrationsOpen: boolean; + registrationsModeration: boolean; registrationsAllowlist: boolean; demoMode: boolean; longEvents: boolean; diff --git a/src/types/enums.ts b/src/types/enums.ts index c187bd4fc..8d9fee2c7 100644 --- a/src/types/enums.ts +++ b/src/types/enums.ts @@ -11,6 +11,12 @@ export enum InstancePrivacyType { CUSTOM = "CUSTOM", } +export enum registrationsModeType { + CLOSE = "CLOSE", + OPEN = "OPEN", + MODERATED = "MODERATED", +} + export enum ICurrentUserRole { USER = "USER", MODERATOR = "MODERATOR", diff --git a/src/views/Admin/SettingsView.vue b/src/views/Admin/SettingsView.vue index a5b78f86e..b252b5362 100644 --- a/src/views/Admin/SettingsView.vue +++ b/src/views/Admin/SettingsView.vue @@ -123,17 +123,36 @@ --> - -

- {{ t("Registration is allowed, anyone can register.") }} -

-

- {{ t("Registration is closed.") }} -

-
+
+ + {{ t("Registration is closed.") }} + + + {{ + t("Registration is allowed, anyone can register.") + }} + + + {{ + t("Registration is moderated, new user must be validated.") + }} + +