David Ko commited on
Commit
630e9fb
ยท
1 Parent(s): b4662d6

Redirect-on-expiry: fresh required on /api/status and injected heartbeat in index.html responses to auto-redirect to /login when session expires

Browse files
Files changed (1) hide show
  1. api.py +117 -15
api.py CHANGED
@@ -3,7 +3,7 @@
3
  import os
4
  os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
5
 
6
- from flask import Flask, request, jsonify, send_from_directory, redirect, url_for, session, render_template_string
7
  from datetime import timedelta
8
  import torch
9
  from PIL import Image
@@ -18,7 +18,16 @@ import time
18
  from flask_cors import CORS
19
  import json
20
  import sys
21
- from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
 
 
 
 
 
 
 
 
 
22
 
23
  # Fix for SQLite3 version compatibility with ChromaDB
24
  try:
@@ -33,8 +42,8 @@ from chromadb.utils import embedding_functions
33
  app = Flask(__name__, static_folder='static')
34
  app.secret_key = 'your_secret_key_here' # ์„ธ์…˜ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ๋น„๋ฐ€ ํ‚ค
35
  app.config['CORS_HEADERS'] = 'Content-Type'
36
- # Remember cookie (Flask-Login)
37
- app.config['REMEMBER_COOKIE_DURATION'] = timedelta(days=30)
38
  app.config['REMEMBER_COOKIE_SECURE'] = True # Spaces uses HTTPS
39
  app.config['REMEMBER_COOKIE_HTTPONLY'] = True
40
  app.config['REMEMBER_COOKIE_SAMESITE'] = 'None'
@@ -1208,7 +1217,19 @@ def logout():
1208
  session.clear()
1209
  except Exception as e:
1210
  print(f"[DEBUG] Error clearing session on logout: {e}")
1211
- return redirect(url_for('login'))
 
 
 
 
 
 
 
 
 
 
 
 
1212
 
1213
  # ์ •์  ํŒŒ์ผ ์„œ๋น™์„ ์œ„ํ•œ ๋ผ์šฐํŠธ (๋กœ๊ทธ์ธ ๋ถˆํ•„์š”)
1214
  @app.route('/static/<path:filename>')
@@ -1224,8 +1245,8 @@ def serve_index_html():
1224
  print(f"Request to /index.html - Cookies: {request.cookies}")
1225
  print(f"Request to /index.html - User authenticated: {current_user.is_authenticated}")
1226
 
1227
- # ์ธ์ฆ ํ™•์ธ
1228
- if not current_user.is_authenticated:
1229
  print("User not authenticated, redirecting to login")
1230
  return redirect(url_for('login'))
1231
 
@@ -1238,7 +1259,45 @@ def serve_index_html():
1238
  session['username'] = current_user.username
1239
  session.modified = True
1240
 
1241
- resp = send_from_directory(app.static_folder, 'index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1242
  # Prevent sensitive pages from being cached
1243
  resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1244
  resp.headers['Pragma'] = 'no-cache'
@@ -1248,7 +1307,7 @@ def serve_index_html():
1248
  # ๊ธฐ๋ณธ ๊ฒฝ๋กœ ๋ฐ ๊ธฐํƒ€ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ (๋กœ๊ทธ์ธ ํ•„์š”)
1249
  @app.route('/', defaults={'path': ''}, methods=['GET'])
1250
  @app.route('/<path:path>', methods=['GET'])
1251
- @login_required
1252
  def serve_react(path):
1253
  """Serve React frontend"""
1254
  print(f"Serving React frontend for path: {path}, user: {current_user.username if current_user.is_authenticated else 'not authenticated'}")
@@ -1260,15 +1319,58 @@ def serve_react(path):
1260
  resp.headers['Expires'] = '0'
1261
  return resp
1262
  else:
1263
- # React ์•ฑ์˜ index.html ์„œ๋น™
1264
- resp = send_from_directory(app.static_folder, 'index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1265
  resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1266
  resp.headers['Pragma'] = 'no-cache'
1267
  resp.headers['Expires'] = '0'
1268
  return resp
1269
 
1270
  @app.route('/similar-images', methods=['GET'])
1271
- @login_required
1272
  def similar_images_page():
1273
  """Serve similar images search page"""
1274
  resp = send_from_directory(app.static_folder, 'similar-images.html')
@@ -1278,7 +1380,7 @@ def similar_images_page():
1278
  return resp
1279
 
1280
  @app.route('/object-detection-search', methods=['GET'])
1281
- @login_required
1282
  def object_detection_search_page():
1283
  """Serve object detection search page"""
1284
  resp = send_from_directory(app.static_folder, 'object-detection-search.html')
@@ -1288,7 +1390,7 @@ def object_detection_search_page():
1288
  return resp
1289
 
1290
  @app.route('/model-vector-db', methods=['GET'])
1291
- @login_required
1292
  def model_vector_db_page():
1293
  """Serve model vector DB UI page"""
1294
  resp = send_from_directory(app.static_folder, 'model-vector-db.html')
@@ -1298,7 +1400,7 @@ def model_vector_db_page():
1298
  return resp
1299
 
1300
  @app.route('/api/status', methods=['GET'])
1301
- @login_required
1302
  def status():
1303
  return jsonify({
1304
  "status": "online",
 
3
  import os
4
  os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
5
 
6
+ from flask import Flask, request, jsonify, send_from_directory, redirect, url_for, session, render_template_string, make_response
7
  from datetime import timedelta
8
  import torch
9
  from PIL import Image
 
18
  from flask_cors import CORS
19
  import json
20
  import sys
21
+ from flask_login import (
22
+ LoginManager,
23
+ UserMixin,
24
+ login_user,
25
+ logout_user,
26
+ login_required,
27
+ current_user,
28
+ fresh_login_required,
29
+ login_fresh,
30
+ )
31
 
32
  # Fix for SQLite3 version compatibility with ChromaDB
33
  try:
 
42
  app = Flask(__name__, static_folder='static')
43
  app.secret_key = 'your_secret_key_here' # ์„ธ์…˜ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ๋น„๋ฐ€ ํ‚ค
44
  app.config['CORS_HEADERS'] = 'Content-Type'
45
+ # Remember cookie (Flask-Login) โ€” minimize duration to prevent auto re-login
46
+ app.config['REMEMBER_COOKIE_DURATION'] = timedelta(seconds=1)
47
  app.config['REMEMBER_COOKIE_SECURE'] = True # Spaces uses HTTPS
48
  app.config['REMEMBER_COOKIE_HTTPONLY'] = True
49
  app.config['REMEMBER_COOKIE_SAMESITE'] = 'None'
 
1217
  session.clear()
1218
  except Exception as e:
1219
  print(f"[DEBUG] Error clearing session on logout: {e}")
1220
+ # Ensure remember cookie is removed by setting an expired cookie
1221
+ resp = redirect(url_for('login'))
1222
+ try:
1223
+ resp.delete_cookie(
1224
+ key='remember_token',
1225
+ path='/',
1226
+ samesite='None',
1227
+ secure=True,
1228
+ httponly=True,
1229
+ )
1230
+ except Exception as e:
1231
+ print(f"[DEBUG] Error deleting remember_token cookie: {e}")
1232
+ return resp
1233
 
1234
  # ์ •์  ํŒŒ์ผ ์„œ๋น™์„ ์œ„ํ•œ ๋ผ์šฐํŠธ (๋กœ๊ทธ์ธ ๋ถˆํ•„์š”)
1235
  @app.route('/static/<path:filename>')
 
1245
  print(f"Request to /index.html - Cookies: {request.cookies}")
1246
  print(f"Request to /index.html - User authenticated: {current_user.is_authenticated}")
1247
 
1248
+ # ์ธ์ฆ ํ™•์ธ (fresh session only)
1249
+ if not current_user.is_authenticated or not login_fresh():
1250
  print("User not authenticated, redirecting to login")
1251
  return redirect(url_for('login'))
1252
 
 
1259
  session['username'] = current_user.username
1260
  session.modified = True
1261
 
1262
+ # index.html์„ ์ฝ์–ด ํ•˜ํŠธ๋น„ํŠธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฃผ์ž…
1263
+ index_path = os.path.join(app.static_folder, 'index.html')
1264
+ try:
1265
+ with open(index_path, 'r', encoding='utf-8') as f:
1266
+ html = f.read()
1267
+ except Exception as e:
1268
+ print(f"[DEBUG] Failed to read index.html for injection: {e}")
1269
+ return send_from_directory(app.static_folder, 'index.html')
1270
+
1271
+ heartbeat_script = """
1272
+ <script>
1273
+ (function(){
1274
+ function checkSession(){
1275
+ fetch('/api/status', {credentials: 'include'}).then(function(res){
1276
+ if(res.status !== 200){
1277
+ window.location.href = '/login';
1278
+ }
1279
+ }).catch(function(){
1280
+ // ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ๋“ฑ๋„ ๋กœ๊ทธ์ธ์œผ๋กœ ์œ ๋„
1281
+ window.location.href = '/login';
1282
+ });
1283
+ }
1284
+ // ์ฒซ ์ฒดํฌ + ์ฃผ๊ธฐ์  ์ฒดํฌ(30์ดˆ)
1285
+ checkSession();
1286
+ setInterval(checkSession, 30000);
1287
+ })();
1288
+ </script>
1289
+ """
1290
+
1291
+ try:
1292
+ if '</body>' in html:
1293
+ html = html.replace('</body>', heartbeat_script + '</body>')
1294
+ else:
1295
+ html = html + heartbeat_script
1296
+ except Exception as e:
1297
+ print(f"[DEBUG] Failed to inject heartbeat script: {e}")
1298
+ return send_from_directory(app.static_folder, 'index.html')
1299
+
1300
+ resp = make_response(html)
1301
  # Prevent sensitive pages from being cached
1302
  resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1303
  resp.headers['Pragma'] = 'no-cache'
 
1307
  # ๊ธฐ๋ณธ ๊ฒฝ๋กœ ๋ฐ ๊ธฐํƒ€ ๊ฒฝ๋กœ ์ฒ˜๋ฆฌ (๋กœ๊ทธ์ธ ํ•„์š”)
1308
  @app.route('/', defaults={'path': ''}, methods=['GET'])
1309
  @app.route('/<path:path>', methods=['GET'])
1310
+ @fresh_login_required
1311
  def serve_react(path):
1312
  """Serve React frontend"""
1313
  print(f"Serving React frontend for path: {path}, user: {current_user.username if current_user.is_authenticated else 'not authenticated'}")
 
1319
  resp.headers['Expires'] = '0'
1320
  return resp
1321
  else:
1322
+ # React ์•ฑ์˜ index.html ์„œ๋น™ (ํ•˜ํŠธ๋น„๏ฟฝ๏ฟฝ ์Šคํฌ๋ฆฝํŠธ ์ฃผ์ž…)
1323
+ index_path = os.path.join(app.static_folder, 'index.html')
1324
+ try:
1325
+ with open(index_path, 'r', encoding='utf-8') as f:
1326
+ html = f.read()
1327
+ except Exception as e:
1328
+ print(f"[DEBUG] Failed to read index.html for injection (serve_react): {e}")
1329
+ resp = send_from_directory(app.static_folder, 'index.html')
1330
+ resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1331
+ resp.headers['Pragma'] = 'no-cache'
1332
+ resp.headers['Expires'] = '0'
1333
+ return resp
1334
+
1335
+ heartbeat_script = """
1336
+ <script>
1337
+ (function(){
1338
+ function checkSession(){
1339
+ fetch('/api/status', {credentials: 'include'}).then(function(res){
1340
+ if(res.status !== 200){
1341
+ window.location.href = '/login';
1342
+ }
1343
+ }).catch(function(){
1344
+ window.location.href = '/login';
1345
+ });
1346
+ }
1347
+ checkSession();
1348
+ setInterval(checkSession, 30000);
1349
+ })();
1350
+ </script>
1351
+ """
1352
+
1353
+ try:
1354
+ if '</body>' in html:
1355
+ html = html.replace('</body>', heartbeat_script + '</body>')
1356
+ else:
1357
+ html = html + heartbeat_script
1358
+ except Exception as e:
1359
+ print(f"[DEBUG] Failed to inject heartbeat script (serve_react): {e}")
1360
+ resp = send_from_directory(app.static_folder, 'index.html')
1361
+ resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1362
+ resp.headers['Pragma'] = 'no-cache'
1363
+ resp.headers['Expires'] = '0'
1364
+ return resp
1365
+
1366
+ resp = make_response(html)
1367
  resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
1368
  resp.headers['Pragma'] = 'no-cache'
1369
  resp.headers['Expires'] = '0'
1370
  return resp
1371
 
1372
  @app.route('/similar-images', methods=['GET'])
1373
+ @fresh_login_required
1374
  def similar_images_page():
1375
  """Serve similar images search page"""
1376
  resp = send_from_directory(app.static_folder, 'similar-images.html')
 
1380
  return resp
1381
 
1382
  @app.route('/object-detection-search', methods=['GET'])
1383
+ @fresh_login_required
1384
  def object_detection_search_page():
1385
  """Serve object detection search page"""
1386
  resp = send_from_directory(app.static_folder, 'object-detection-search.html')
 
1390
  return resp
1391
 
1392
  @app.route('/model-vector-db', methods=['GET'])
1393
+ @fresh_login_required
1394
  def model_vector_db_page():
1395
  """Serve model vector DB UI page"""
1396
  resp = send_from_directory(app.static_folder, 'model-vector-db.html')
 
1400
  return resp
1401
 
1402
  @app.route('/api/status', methods=['GET'])
1403
+ @fresh_login_required
1404
  def status():
1405
  return jsonify({
1406
  "status": "online",