diff --git a/PanTS-Demo/src/routes/UploadPage.tsx b/PanTS-Demo/src/routes/UploadPage.tsx
index 1310d39..e868d1f 100644
--- a/PanTS-Demo/src/routes/UploadPage.tsx
+++ b/PanTS-Demo/src/routes/UploadPage.tsx
@@ -7,12 +7,6 @@ import React, {
useState,
} from "react";
-// Mirrors the backend's REQUIRE_AUTH_FOR_INFERENCE. Off by default so anonymous
-// visitors can still run inference on the deployed site, which has no working
-// sign-in yet. Set VITE_REQUIRE_AUTH_FOR_INFERENCE=true to restore the gate.
-const REQUIRE_AUTH_FOR_INFERENCE =
- String(import.meta.env.VITE_REQUIRE_AUTH_FOR_INFERENCE || "").toLowerCase() === "true";
-
const MODEL_OPTIONS: { id: string; label: string; desc: string }[] = [
{
id: "None",
@@ -105,11 +99,11 @@ type SelectedItem =
const UploadPage: React.FC = () => {
const navigate = useNavigate();
+ // Running inference requires an account, so any upload action while signed
+ // out opens the sign-up popup instead of proceeding.
const { isAuthenticated, promptAuth } = useAuth();
- // Signed-out uploads open the sign-up popup instead of proceeding, but only
- // while the account gate is on (see REQUIRE_AUTH_FOR_INFERENCE above).
const ensureAccount = (): boolean => {
- if (!REQUIRE_AUTH_FOR_INFERENCE || isAuthenticated) return true;
+ if (isAuthenticated) return true;
promptAuth("signup");
return false;
};
@@ -203,7 +197,7 @@ const UploadPage: React.FC = () => {
e.preventDefault();
setIsDragOver(false);
// Inlined (not via ensureAccount) so the memoized closure sees fresh auth.
- if (REQUIRE_AUTH_FOR_INFERENCE && !isAuthenticated) { promptAuth("signup"); return; }
+ if (!isAuthenticated) { promptAuth("signup"); return; }
if (!e.dataTransfer.files) return;
const filteredFiles = Array.from(e.dataTransfer.files).filter((file) =>
allowedExtensions.some((ext) => file.name.toLowerCase().endsWith(ext)),
@@ -1463,7 +1457,7 @@ const UploadPage: React.FC = () => {
{" "}
- to keep track of your scans and get notified when they're done.
+ to run inference on the server and get notified when it's done.
)}
diff --git a/flask-server/.env.example b/flask-server/.env.example
index efca2c5..e884c9f 100644
--- a/flask-server/.env.example
+++ b/flask-server/.env.example
@@ -53,12 +53,6 @@ CANCERVERSE_LOWRES_PATH=/home/visitor/cancerverse_lowres
# Send the session cookie only over HTTPS. Set true in production.
# SESSION_COOKIE_SECURE=true
-# Require a signed-in account to run inference. Off by default so anonymous
-# visitors keep working; anonymous runs are owned by the system user. Turn on
-# once sign-in actually works in production (i.e. once the site is on HTTPS).
-# The frontend has a matching VITE_REQUIRE_AUTH_FOR_INFERENCE — set both.
-# REQUIRE_AUTH_FOR_INFERENCE=true
-
# Trust X-Forwarded-Proto/Host from the reverse proxy. REQUIRED in production:
# without it the OAuth redirect_uri is built as http:// and Google/GitHub reject
# it as a mismatch. Leave unset in dev — nothing trusted sits in front of the
diff --git a/flask-server/api/api_blueprint.py b/flask-server/api/api_blueprint.py
index 2165287..51b2c95 100644
--- a/flask-server/api/api_blueprint.py
+++ b/flask-server/api/api_blueprint.py
@@ -14,11 +14,10 @@
)
from services.segmentation_metrics import calculate_session_metrics
from services import job_store
-from api.auth import current_user
+from api.auth import current_user, require_auth
from services.search_ranking import rank_quality_results
from services.site_normalization import site_country_label, split_site_codes
from models.application_session import ApplicationSession
-from models.user import SYSTEM_USER_ID
from models.combined_labels import CombinedLabels
from models.base import db
from constants import Constants
@@ -48,12 +47,6 @@
# 建立 blueprint
api_blueprint = Blueprint("api", __name__)
-
-# Whether running inference requires a signed-in account. Off by default so the
-# public site keeps working for anonymous visitors; anonymous runs are recorded
-# against the system user. Set true once sign-in is actually reachable in prod
-# (i.e. once the site is on HTTPS and OAuth works).
-REQUIRE_AUTH_FOR_INFERENCE = os.environ.get("REQUIRE_AUTH_FOR_INFERENCE", "false").lower() == "true"
last_session_check = datetime.now()
# Low-res volumes (generated by scripts/make_lowres.py) live on a WRITABLE disk,
@@ -958,15 +951,12 @@ def _uploaded_file_candidate(session_id, uploaded_filename):
def _start_auto_segmentation(session_id, model_name, ct_file=None, server_input_path=None, user_id=None):
if not _is_safe_id(session_id):
return jsonify({"error": "Invalid session ID"}), 400
- # Record ownership on the job when someone is signed in. Anonymous runs fall
- # back to the system user (job.user_id is NOT NULL), unless
- # REQUIRE_AUTH_FOR_INFERENCE is set, in which case they're rejected.
+ # Running inference requires an account; the endpoint's @require_auth
+ # guarantees a user, and we record ownership on the job.
if not user_id:
user_id = (current_user() or {}).get("id")
if not user_id:
- if REQUIRE_AUTH_FOR_INFERENCE:
- return jsonify({"error": "Authentication required"}), 401
- user_id = SYSTEM_USER_ID
+ return jsonify({"error": "Authentication required"}), 401
session_path = os.path.join(SESSIONS_DIR, session_id)
os.makedirs(session_path, exist_ok=True)
@@ -1057,6 +1047,7 @@ def _on_gpu_slot():
return jsonify({"message": "Segmentation started", "session_id": session_id}), 200
@api_blueprint.route('/auto_segment/', methods=['POST'])
+@require_auth
def auto_segment(session_id):
model_name = request.form.get("MODEL_NAME", None)
@@ -1077,6 +1068,7 @@ def auto_segment(session_id):
@api_blueprint.route('/run-epai-inference', methods=['POST'])
@api_blueprint.route('/run-inference', methods=['POST'])
+@require_auth
def run_epai_inference():
"""
Runs ePAI inference with either: