diff --git a/surfsense_backend/alembic/versions/39_add_disable_registration_to_site_configuration.py b/surfsense_backend/alembic/versions/39_add_disable_registration_to_site_configuration.py new file mode 100644 index 000000000..5a9130ea2 --- /dev/null +++ b/surfsense_backend/alembic/versions/39_add_disable_registration_to_site_configuration.py @@ -0,0 +1,31 @@ +"""add disable_registration to site_configuration + +Revision ID: 39_add_disable_registration_to_site_configuration +Revises: 38_add_site_configuration_table +Create Date: 2025-11-18 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '39_add_disable_registration_to_site_configuration' +down_revision: Union[str, None] = '38_add_site_configuration_table' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # Add disable_registration column to site_configuration table + op.add_column( + 'site_configuration', + sa.Column('disable_registration', sa.Boolean(), nullable=False, server_default='false') + ) + + +def downgrade() -> None: + # Remove disable_registration column + op.drop_column('site_configuration', 'disable_registration') diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 3fc0b14f7..7925a8945 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -2,11 +2,12 @@ from contextlib import asynccontextmanager from fastapi import Depends, FastAPI, HTTPException, status from fastapi.middleware.cors import CORSMiddleware +from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware from app.config import config -from app.db import User, create_db_and_tables, get_async_session +from app.db import SiteConfiguration, User, create_db_and_tables, get_async_session from app.routes import router as crud_router from app.schemas import UserCreate, UserRead, UserUpdate from app.users import SECRET, auth_backend, current_active_user, fastapi_users @@ -19,11 +20,24 @@ async def lifespan(app: FastAPI): yield -def registration_allowed(): +async def registration_allowed(session: AsyncSession = Depends(get_async_session)): + # Check environment variable first if not config.REGISTRATION_ENABLED: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="Registration is disabled" + status_code=status.HTTP_403_FORBIDDEN, + detail="Registration is disabled by system configuration" ) + + # Check site configuration database toggle + result = await session.execute(select(SiteConfiguration).where(SiteConfiguration.id == 1)) + site_config = result.scalar_one_or_none() + + if site_config and site_config.disable_registration: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Registration is currently disabled. Please contact the administrator if you need access." + ) + return True diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py index 12d07e5fe..f67d06215 100644 --- a/surfsense_backend/app/db.py +++ b/surfsense_backend/app/db.py @@ -411,6 +411,9 @@ class SiteConfiguration(Base): disable_terms_route = Column(Boolean, nullable=False, default=True) disable_privacy_route = Column(Boolean, nullable=False, default=True) + # Registration control + disable_registration = Column(Boolean, nullable=False, default=False) + # Custom text custom_copyright = Column(String(200), nullable=True, default="SurfSense 2025") diff --git a/surfsense_backend/app/schemas/site_configuration.py b/surfsense_backend/app/schemas/site_configuration.py index ca790931b..48329335d 100644 --- a/surfsense_backend/app/schemas/site_configuration.py +++ b/surfsense_backend/app/schemas/site_configuration.py @@ -24,6 +24,9 @@ class SiteConfigurationBase(BaseModel): disable_terms_route: bool = True disable_privacy_route: bool = True + # Registration control + disable_registration: bool = False + # Custom text custom_copyright: str | None = Field(default="SurfSense 2025", max_length=200) @@ -44,6 +47,7 @@ class SiteConfigurationUpdate(SiteConfigurationBase): disable_contact_route: bool | None = None disable_terms_route: bool | None = None disable_privacy_route: bool | None = None + disable_registration: bool | None = None custom_copyright: str | None = None diff --git a/surfsense_web/app/(home)/login/LocalLoginForm.tsx b/surfsense_web/app/(home)/login/LocalLoginForm.tsx index 5bb4cbc40..b3d7d146c 100644 --- a/surfsense_web/app/(home)/login/LocalLoginForm.tsx +++ b/surfsense_web/app/(home)/login/LocalLoginForm.tsx @@ -8,12 +8,14 @@ import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms"; +import { useSiteConfig } from "@/contexts/SiteConfigContext"; import { getAuthErrorDetails, isNetworkError, shouldRetry } from "@/lib/auth-errors"; import { ValidationError } from "@/lib/error"; export function LocalLoginForm() { const t = useTranslations("auth"); const tCommon = useTranslations("common"); + const { config } = useSiteConfig(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); @@ -227,17 +229,19 @@ export function LocalLoginForm() { -
- {t("dont_have_account")}{" "} - - {t("sign_up")} - -
-+ {t("dont_have_account")}{" "} + + {t("sign_up")} + +
++ Control user registration availability. When disabled, the Sign Up link will be hidden + and the registration page will show a 404 error. The backend will also block registration + requests with a 403 error. +
+