Spaces:
Running
Running
David Ko
commited on
Commit
ยท
b4662d6
1
Parent(s):
ac9962b
Security/session hardening: absolute 2-min expiry (SESSION_REFRESH_EACH_REQUEST=False), disable remember, anti-autofill login form, no-store cache on protected routes, clear session on logout
Browse files
api.py
CHANGED
@@ -48,7 +48,8 @@ CORS(app) # Enable CORS for all routes
|
|
48 |
# ์ํฌ๋ฆฟ ํค ์ค์ (์ธ์
์ํธํ์ ์ฌ์ฉ)
|
49 |
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'vision_llm_agent_secret_key')
|
50 |
app.config['SESSION_TYPE'] = 'filesystem'
|
51 |
-
app.config['PERMANENT_SESSION_LIFETIME'] = 120 # ์ธ์
์ ํจ ์๊ฐ (
|
|
|
52 |
|
53 |
# Flask-Login ์ค์
|
54 |
login_manager = LoginManager()
|
@@ -1139,14 +1140,17 @@ LOGIN_TEMPLATE = '''
|
|
1139 |
<body>
|
1140 |
<div class="login-container">
|
1141 |
<h1>Vision LLM Agent</h1>
|
1142 |
-
<form action="/login" method="post">
|
|
|
|
|
|
|
1143 |
<div class="form-group">
|
1144 |
<label for="username">Username</label>
|
1145 |
-
<input type="text" id="username" name="username" required>
|
1146 |
</div>
|
1147 |
<div class="form-group">
|
1148 |
<label for="password">Password</label>
|
1149 |
-
<input type="password" id="password" name="password" required>
|
1150 |
</div>
|
1151 |
<button type="submit">Login</button>
|
1152 |
{% if error %}
|
@@ -1199,6 +1203,11 @@ def login():
|
|
1199 |
@app.route('/logout')
|
1200 |
def logout():
|
1201 |
logout_user()
|
|
|
|
|
|
|
|
|
|
|
1202 |
return redirect(url_for('login'))
|
1203 |
|
1204 |
# ์ ์ ํ์ผ ์๋น์ ์ํ ๋ผ์ฐํธ (๋ก๊ทธ์ธ ๋ถํ์)
|
@@ -1229,7 +1238,12 @@ def serve_index_html():
|
|
1229 |
session['username'] = current_user.username
|
1230 |
session.modified = True
|
1231 |
|
1232 |
-
|
|
|
|
|
|
|
|
|
|
|
1233 |
|
1234 |
# ๊ธฐ๋ณธ ๊ฒฝ๋ก ๋ฐ ๊ธฐํ ๊ฒฝ๋ก ์ฒ๋ฆฌ (๋ก๊ทธ์ธ ํ์)
|
1235 |
@app.route('/', defaults={'path': ''}, methods=['GET'])
|
@@ -1240,28 +1254,48 @@ def serve_react(path):
|
|
1240 |
print(f"Serving React frontend for path: {path}, user: {current_user.username if current_user.is_authenticated else 'not authenticated'}")
|
1241 |
# ์ ์ ํ์ผ ์ฒ๋ฆฌ๋ ์ด์ ๋ณ๋ ๋ผ์ฐํธ์์ ์ฒ๋ฆฌ
|
1242 |
if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
|
1243 |
-
|
|
|
|
|
|
|
|
|
1244 |
else:
|
1245 |
# React ์ฑ์ index.html ์๋น
|
1246 |
-
|
|
|
|
|
|
|
|
|
1247 |
|
1248 |
@app.route('/similar-images', methods=['GET'])
|
1249 |
@login_required
|
1250 |
def similar_images_page():
|
1251 |
"""Serve similar images search page"""
|
1252 |
-
|
|
|
|
|
|
|
|
|
1253 |
|
1254 |
@app.route('/object-detection-search', methods=['GET'])
|
1255 |
@login_required
|
1256 |
def object_detection_search_page():
|
1257 |
"""Serve object detection search page"""
|
1258 |
-
|
|
|
|
|
|
|
|
|
1259 |
|
1260 |
@app.route('/model-vector-db', methods=['GET'])
|
1261 |
@login_required
|
1262 |
def model_vector_db_page():
|
1263 |
"""Serve model vector DB UI page"""
|
1264 |
-
|
|
|
|
|
|
|
|
|
1265 |
|
1266 |
@app.route('/api/status', methods=['GET'])
|
1267 |
@login_required
|
|
|
48 |
# ์ํฌ๋ฆฟ ํค ์ค์ (์ธ์
์ํธํ์ ์ฌ์ฉ)
|
49 |
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'vision_llm_agent_secret_key')
|
50 |
app.config['SESSION_TYPE'] = 'filesystem'
|
51 |
+
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(seconds=120) # ์ธ์
์ ํจ ์๊ฐ (2๋ถ)
|
52 |
+
app.config['SESSION_REFRESH_EACH_REQUEST'] = False # ์ ๋ ๋ง๋ฃ(๋ก๊ทธ์ธ ๊ธฐ์ค 2๋ถ ํ ๋ง๋ฃ)
|
53 |
|
54 |
# Flask-Login ์ค์
|
55 |
login_manager = LoginManager()
|
|
|
1140 |
<body>
|
1141 |
<div class="login-container">
|
1142 |
<h1>Vision LLM Agent</h1>
|
1143 |
+
<form action="/login" method="post" autocomplete="off">
|
1144 |
+
<!-- hidden dummy fields to discourage Chrome autofill -->
|
1145 |
+
<input type="text" name="fakeusernameremembered" style="display:none" tabindex="-1" autocomplete="off">
|
1146 |
+
<input type="password" name="fakepasswordremembered" style="display:none" tabindex="-1" autocomplete="off">
|
1147 |
<div class="form-group">
|
1148 |
<label for="username">Username</label>
|
1149 |
+
<input type="text" id="username" name="username" required autocomplete="username" autocapitalize="none" autocorrect="off" spellcheck="false">
|
1150 |
</div>
|
1151 |
<div class="form-group">
|
1152 |
<label for="password">Password</label>
|
1153 |
+
<input type="password" id="password" name="password" required autocomplete="current-password" autocapitalize="none" autocorrect="off" spellcheck="false">
|
1154 |
</div>
|
1155 |
<button type="submit">Login</button>
|
1156 |
{% if error %}
|
|
|
1203 |
@app.route('/logout')
|
1204 |
def logout():
|
1205 |
logout_user()
|
1206 |
+
# Clear server-side session fully
|
1207 |
+
try:
|
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 |
# ์ ์ ํ์ผ ์๋น์ ์ํ ๋ผ์ฐํธ (๋ก๊ทธ์ธ ๋ถํ์)
|
|
|
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'
|
1245 |
+
resp.headers['Expires'] = '0'
|
1246 |
+
return resp
|
1247 |
|
1248 |
# ๊ธฐ๋ณธ ๊ฒฝ๋ก ๋ฐ ๊ธฐํ ๊ฒฝ๋ก ์ฒ๋ฆฌ (๋ก๊ทธ์ธ ํ์)
|
1249 |
@app.route('/', defaults={'path': ''}, methods=['GET'])
|
|
|
1254 |
print(f"Serving React frontend for path: {path}, user: {current_user.username if current_user.is_authenticated else 'not authenticated'}")
|
1255 |
# ์ ์ ํ์ผ ์ฒ๋ฆฌ๋ ์ด์ ๋ณ๋ ๋ผ์ฐํธ์์ ์ฒ๋ฆฌ
|
1256 |
if path != "" and os.path.exists(os.path.join(app.static_folder, path)):
|
1257 |
+
resp = send_from_directory(app.static_folder, path)
|
1258 |
+
resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
1259 |
+
resp.headers['Pragma'] = 'no-cache'
|
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')
|
1275 |
+
resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
1276 |
+
resp.headers['Pragma'] = 'no-cache'
|
1277 |
+
resp.headers['Expires'] = '0'
|
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')
|
1285 |
+
resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
1286 |
+
resp.headers['Pragma'] = 'no-cache'
|
1287 |
+
resp.headers['Expires'] = '0'
|
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')
|
1295 |
+
resp.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
1296 |
+
resp.headers['Pragma'] = 'no-cache'
|
1297 |
+
resp.headers['Expires'] = '0'
|
1298 |
+
return resp
|
1299 |
|
1300 |
@app.route('/api/status', methods=['GET'])
|
1301 |
@login_required
|