Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions PanTS-Demo/src/routes/UploadPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@
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",
Expand Down Expand Up @@ -105,11 +99,11 @@

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;
};
Expand Down Expand Up @@ -203,7 +197,7 @@
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)),
Expand All @@ -220,7 +214,7 @@
file: f,
})),
]);
}, [isAuthenticated, promptAuth]);

Check warning on line 217 in PanTS-Demo/src/routes/UploadPage.tsx

View workflow job for this annotation

GitHub Actions / Frontend (Node 22)

React Hook useCallback has a missing dependency: 'allowedExtensions'. Either include it or remove the dependency array

Check warning on line 217 in PanTS-Demo/src/routes/UploadPage.tsx

View workflow job for this annotation

GitHub Actions / Frontend (Node 20)

React Hook useCallback has a missing dependency: 'allowedExtensions'. Either include it or remove the dependency array

const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -411,7 +405,7 @@
cancelled = true;
stopAllPolling();
};
}, []);

Check warning on line 408 in PanTS-Demo/src/routes/UploadPage.tsx

View workflow job for this annotation

GitHub Actions / Frontend (Node 22)

React Hook useEffect has missing dependencies: 'runUpload' and 'startInferencePolling'. Either include them or remove the dependency array

Check warning on line 408 in PanTS-Demo/src/routes/UploadPage.tsx

View workflow job for this annotation

GitHub Actions / Frontend (Node 20)

React Hook useEffect has missing dependencies: 'runUpload' and 'startInferencePolling'. Either include them or remove the dependency array

// Only warn before an unload if the current upload could NOT be stored in
// IndexedDB (quota/private-mode) - otherwise an interrupted upload resumes
Expand Down Expand Up @@ -1463,7 +1457,7 @@
<button type="button" className="upload-account-link" onClick={() => promptAuth("signup")}>
Sign in
</button>{" "}
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.
</span>
</div>
)}
Expand Down
6 changes: 0 additions & 6 deletions flask-server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 6 additions & 14 deletions flask-server/api/api_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -1057,6 +1047,7 @@ def _on_gpu_slot():
return jsonify({"message": "Segmentation started", "session_id": session_id}), 200

@api_blueprint.route('/auto_segment/<session_id>', methods=['POST'])
@require_auth
def auto_segment(session_id):

model_name = request.form.get("MODEL_NAME", None)
Expand All @@ -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:
Expand Down
Loading