Make self-service signup opt-in via SIGNUP_ENABLED
/users/signup answers 403 unless SIGNUP_ENABLED is set. A public GET /utils/config/ tells the login page, which hides the sign-up link, and the signup page explains that an administrator creates accounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
This commit is contained in:
@@ -154,6 +154,8 @@ def register_user(session: SessionDep, user_in: UserRegister) -> Any:
|
||||
"""
|
||||
Create new user without the need to be logged in.
|
||||
"""
|
||||
if not settings.SIGNUP_ENABLED:
|
||||
raise HTTPException(status_code=403, detail="Signup is disabled")
|
||||
user = crud.get_user_by_email(session=session, email=user_in.email)
|
||||
if user:
|
||||
raise HTTPException(
|
||||
|
||||
@@ -3,9 +3,11 @@ from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from pydantic import BaseModel
|
||||
from pydantic.networks import EmailStr
|
||||
|
||||
from fluksio.api.deps import get_current_active_superuser
|
||||
from fluksio.core.config import settings
|
||||
from fluksio.flow.state import RedisState
|
||||
from fluksio.models import Message
|
||||
from fluksio.utils import generate_test_email, send_email
|
||||
@@ -31,6 +33,18 @@ def test_email(email_to: EmailStr) -> Message:
|
||||
return Message(message="Test email sent")
|
||||
|
||||
|
||||
class PublicConfig(BaseModel):
|
||||
"""What a browser needs to know about this instance before signing in."""
|
||||
|
||||
signup_enabled: bool
|
||||
|
||||
|
||||
@router.get("/config/", response_model=PublicConfig)
|
||||
def read_config() -> Any:
|
||||
"""The settings the login page reads. Unauthenticated, so nothing secret."""
|
||||
return PublicConfig(signup_enabled=settings.SIGNUP_ENABLED)
|
||||
|
||||
|
||||
@router.get("/health-check/")
|
||||
async def health_check() -> bool:
|
||||
return True
|
||||
|
||||
@@ -99,6 +99,10 @@ class Settings(BaseSettings):
|
||||
# opt-in on top of ENVIRONMENT=local, so a deployment that merely kept the
|
||||
# default environment never exposes them.
|
||||
PRIVATE_API_ENABLED: bool = False
|
||||
# Whether anyone who reaches the login page may create an account. Off
|
||||
# unless asked for: an account can change what flows do, so who gets one
|
||||
# is a superuser's call (Admin → Users).
|
||||
SIGNUP_ENABLED: bool = False
|
||||
DOMAIN: str = "localhost"
|
||||
OAUTH_PRIVATE_KEY_FILE: Path = Path("flow-data/oauth-key.pem")
|
||||
# Written only when someone enrols this instance with a portal.
|
||||
|
||||
@@ -316,15 +316,32 @@ def test_update_password_me_same_password_error(
|
||||
)
|
||||
|
||||
|
||||
def test_signup_is_off_unless_enabled(client: TestClient, db: Session) -> None:
|
||||
"""An instance nobody configured creates accounts only through Admin."""
|
||||
assert client.get(f"{settings.API_V1_STR}/utils/config/").json() == {
|
||||
"signup_enabled": False
|
||||
}
|
||||
username = random_email()
|
||||
data = {"email": username, "password": random_lower_string(), "full_name": "x"}
|
||||
r = client.post(f"{settings.API_V1_STR}/users/signup", json=data)
|
||||
assert r.status_code == 403
|
||||
assert r.json()["detail"] == "Signup is disabled"
|
||||
assert db.exec(select(User).where(User.email == username)).first() is None
|
||||
|
||||
|
||||
def test_register_user(client: TestClient, db: Session) -> None:
|
||||
username = random_email()
|
||||
password = random_lower_string()
|
||||
full_name = random_lower_string()
|
||||
data = {"email": username, "password": password, "full_name": full_name}
|
||||
r = client.post(
|
||||
f"{settings.API_V1_STR}/users/signup",
|
||||
json=data,
|
||||
)
|
||||
with patch("fluksio.core.config.settings.SIGNUP_ENABLED", True):
|
||||
assert client.get(f"{settings.API_V1_STR}/utils/config/").json() == {
|
||||
"signup_enabled": True
|
||||
}
|
||||
r = client.post(
|
||||
f"{settings.API_V1_STR}/users/signup",
|
||||
json=data,
|
||||
)
|
||||
assert r.status_code == 200
|
||||
created_user = r.json()
|
||||
assert created_user["email"] == username
|
||||
@@ -347,10 +364,11 @@ def test_register_user_already_exists_error(client: TestClient) -> None:
|
||||
"password": password,
|
||||
"full_name": full_name,
|
||||
}
|
||||
r = client.post(
|
||||
f"{settings.API_V1_STR}/users/signup",
|
||||
json=data,
|
||||
)
|
||||
with patch("fluksio.core.config.settings.SIGNUP_ENABLED", True):
|
||||
r = client.post(
|
||||
f"{settings.API_V1_STR}/users/signup",
|
||||
json=data,
|
||||
)
|
||||
assert r.status_code == 400
|
||||
assert r.json()["detail"] == "The user with this email already exists in the system"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user