mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-16 11:31:04 +02:00
fix: gate OSS email/password auth endpoints outside local auth mode (#500)
* fix: gate OSS email/password auth endpoints outside local auth mode The /auth signup/login/me routes were mounted unconditionally, so the SaaS deployment accepted unauthenticated signups that created oss_* provider-id users (and auto-provisioned MPS service keys) bypassing Stack Auth entirely. Gate them with a router-level dependency that 404s when AUTH_PROVIDER is not "local", rather than conditionally mounting the router, so the OpenAPI spec and the clients generated from it stay identical across deployment modes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: keep current user route available in stack auth --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
45afc47473
commit
4fb3193eb5
3 changed files with 91 additions and 3 deletions
|
|
@ -5,7 +5,11 @@ from api.db import db_client
|
||||||
from api.db.models import UserModel
|
from api.db.models import UserModel
|
||||||
from api.enums import OrganizationConfigurationKey, PostHogEvent
|
from api.enums import OrganizationConfigurationKey, PostHogEvent
|
||||||
from api.schemas.auth import AuthResponse, LoginRequest, SignupRequest, UserResponse
|
from api.schemas.auth import AuthResponse, LoginRequest, SignupRequest, UserResponse
|
||||||
from api.services.auth.depends import create_user_configuration_with_mps_key, get_user
|
from api.services.auth.depends import (
|
||||||
|
create_user_configuration_with_mps_key,
|
||||||
|
get_user,
|
||||||
|
require_local_auth,
|
||||||
|
)
|
||||||
from api.services.configuration.ai_model_configuration import (
|
from api.services.configuration.ai_model_configuration import (
|
||||||
convert_legacy_ai_model_configuration_to_v2,
|
convert_legacy_ai_model_configuration_to_v2,
|
||||||
)
|
)
|
||||||
|
|
@ -18,7 +22,11 @@ router = APIRouter(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/signup", response_model=AuthResponse)
|
@router.post(
|
||||||
|
"/signup",
|
||||||
|
response_model=AuthResponse,
|
||||||
|
dependencies=[Depends(require_local_auth)],
|
||||||
|
)
|
||||||
async def signup(request: SignupRequest):
|
async def signup(request: SignupRequest):
|
||||||
# Check if email is already taken
|
# Check if email is already taken
|
||||||
existing_user = await db_client.get_user_by_email(request.email)
|
existing_user = await db_client.get_user_by_email(request.email)
|
||||||
|
|
@ -85,7 +93,11 @@ async def signup(request: SignupRequest):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=AuthResponse)
|
@router.post(
|
||||||
|
"/login",
|
||||||
|
response_model=AuthResponse,
|
||||||
|
dependencies=[Depends(require_local_auth)],
|
||||||
|
)
|
||||||
async def login(request: LoginRequest):
|
async def login(request: LoginRequest):
|
||||||
# Look up user by email
|
# Look up user by email
|
||||||
user = await db_client.get_user_by_email(request.email)
|
user = await db_client.get_user_by_email(request.email)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,19 @@ from api.services.posthog_client import (
|
||||||
)
|
)
|
||||||
from api.utils.auth import decode_jwt_token
|
from api.utils.auth import decode_jwt_token
|
||||||
|
|
||||||
|
|
||||||
|
async def require_local_auth() -> None:
|
||||||
|
"""Reject email/password auth requests outside OSS (local) deployments.
|
||||||
|
|
||||||
|
The auth router stays mounted in every mode so the OpenAPI spec — and the
|
||||||
|
clients generated from it — don't vary with AUTH_PROVIDER; the gate has to
|
||||||
|
happen at request time. Without it, the SaaS deployment accepts
|
||||||
|
unauthenticated signups that mint oss_* users bypassing Stack Auth.
|
||||||
|
"""
|
||||||
|
if AUTH_PROVIDER != "local":
|
||||||
|
raise HTTPException(status_code=404, detail="Not found")
|
||||||
|
|
||||||
|
|
||||||
POSTHOG_ORGANIZATION_GROUP_TYPE = "organization"
|
POSTHOG_ORGANIZATION_GROUP_TYPE = "organization"
|
||||||
POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY = "uses_mps_billing_v2"
|
POSTHOG_ORGANIZATION_USES_MPS_BILLING_V2_PROPERTY = "uses_mps_billing_v2"
|
||||||
|
|
||||||
|
|
|
||||||
63
api/tests/test_auth_routes.py
Normal file
63
api/tests/test_auth_routes.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from api.routes.auth import router
|
||||||
|
from api.services.auth import depends as auth_depends
|
||||||
|
from api.services.auth.depends import get_user
|
||||||
|
|
||||||
|
|
||||||
|
def _make_test_app() -> FastAPI:
|
||||||
|
app = FastAPI()
|
||||||
|
app.include_router(router)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_mode_hides_email_password_auth_routes(monkeypatch):
|
||||||
|
monkeypatch.setattr(auth_depends, "AUTH_PROVIDER", "stack")
|
||||||
|
client = TestClient(_make_test_app())
|
||||||
|
|
||||||
|
signup_response = client.post(
|
||||||
|
"/auth/signup",
|
||||||
|
json={
|
||||||
|
"email": "user@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
"name": "User",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
login_response = client.post(
|
||||||
|
"/auth/login",
|
||||||
|
json={
|
||||||
|
"email": "user@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert signup_response.status_code == 404
|
||||||
|
assert signup_response.json() == {"detail": "Not found"}
|
||||||
|
assert login_response.status_code == 404
|
||||||
|
assert login_response.json() == {"detail": "Not found"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_stack_mode_keeps_current_user_route_available(monkeypatch):
|
||||||
|
monkeypatch.setattr(auth_depends, "AUTH_PROVIDER", "stack")
|
||||||
|
app = _make_test_app()
|
||||||
|
app.dependency_overrides[get_user] = lambda: SimpleNamespace(
|
||||||
|
id=7,
|
||||||
|
email="user@example.com",
|
||||||
|
selected_organization_id=42,
|
||||||
|
provider_id="stack-user-1",
|
||||||
|
)
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
response = client.get("/auth/me")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {
|
||||||
|
"id": 7,
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": None,
|
||||||
|
"organization_id": 42,
|
||||||
|
"provider_id": "stack-user-1",
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue