Spaces:
Sleeping
Sleeping
Complete token-based authentication system
Browse files- app.py +176 -129
- app/templates/dashboard.html +41 -14
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import gc
|
|
| 5 |
import logging
|
| 6 |
import time
|
| 7 |
import uuid
|
|
|
|
| 8 |
import pymongo
|
| 9 |
from pymongo import MongoClient
|
| 10 |
from bson.binary import Binary
|
|
@@ -67,7 +68,7 @@ app.wsgi_app = ProxyFix(
|
|
| 67 |
x_prefix=0
|
| 68 |
)
|
| 69 |
|
| 70 |
-
print("Flask app initialized with ProxyFix middleware and
|
| 71 |
|
| 72 |
# Create temporary directory for image processing
|
| 73 |
TEMP_DIR = tempfile.mkdtemp()
|
|
@@ -98,6 +99,7 @@ try:
|
|
| 98 |
teachers_collection = db['teachers']
|
| 99 |
attendance_collection = db['attendance']
|
| 100 |
metrics_events = db['metrics_events']
|
|
|
|
| 101 |
|
| 102 |
# Create indexes for better performance
|
| 103 |
students_collection.create_index([("student_id", pymongo.ASCENDING)], unique=True)
|
|
@@ -110,6 +112,8 @@ try:
|
|
| 110 |
metrics_events.create_index([("ts", pymongo.DESCENDING)])
|
| 111 |
metrics_events.create_index([("event", pymongo.ASCENDING)])
|
| 112 |
metrics_events.create_index([("attempt_type", pymongo.ASCENDING)])
|
|
|
|
|
|
|
| 113 |
print("MongoDB connection successful")
|
| 114 |
except Exception as e:
|
| 115 |
print(f"MongoDB connection error: {e}")
|
|
@@ -345,6 +349,45 @@ def recognize_face(image, user_id, user_type='student'):
|
|
| 345 |
"""Legacy wrapper for the new DeepFace recognition"""
|
| 346 |
return recognize_face_deepface(image, user_id, user_type)
|
| 347 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
# ---------------------- Metrics helpers ----------------------
|
| 349 |
def log_metrics_event(event: dict):
|
| 350 |
try:
|
|
@@ -558,10 +601,8 @@ def login():
|
|
| 558 |
student_id = request.form.get('student_id')
|
| 559 |
password = request.form.get('password')
|
| 560 |
|
| 561 |
-
print(f"=== LOGIN DEBUG ===")
|
| 562 |
print(f"Student ID: {student_id}")
|
| 563 |
-
print(f"Request headers: {dict(request.headers)}")
|
| 564 |
-
print(f"User-Agent: {request.headers.get('User-Agent', 'N/A')}")
|
| 565 |
|
| 566 |
if not student_id or not password:
|
| 567 |
flash('Student ID and password are required.', 'danger')
|
|
@@ -571,34 +612,14 @@ def login():
|
|
| 571 |
print(f"Student found: {bool(student)}")
|
| 572 |
|
| 573 |
if student and student.get('password') == password:
|
| 574 |
-
#
|
| 575 |
-
|
| 576 |
-
session.permanent = True # Make session permanent FIRST
|
| 577 |
-
|
| 578 |
-
session['logged_in'] = True
|
| 579 |
-
session['user_type'] = 'student'
|
| 580 |
-
session['student_id'] = student_id
|
| 581 |
-
session['name'] = student.get('name', 'Unknown')
|
| 582 |
-
session['login_time'] = datetime.now().isoformat()
|
| 583 |
-
|
| 584 |
-
print(f"Session after setting: {dict(session)}")
|
| 585 |
-
print(f"Session permanent: {session.permanent}")
|
| 586 |
-
print(f"Session modified: {session.modified}")
|
| 587 |
|
|
|
|
| 588 |
flash('Login successful!', 'success')
|
| 589 |
|
| 590 |
-
#
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
# Add session cookie headers manually for debugging
|
| 594 |
-
response.set_cookie(
|
| 595 |
-
'debug_session',
|
| 596 |
-
f"logged_in_at_{int(datetime.now().timestamp())}",
|
| 597 |
-
max_age=3600,
|
| 598 |
-
httponly=False
|
| 599 |
-
)
|
| 600 |
-
|
| 601 |
-
return response
|
| 602 |
else:
|
| 603 |
print("Invalid credentials")
|
| 604 |
flash('Invalid credentials. Please try again.', 'danger')
|
|
@@ -661,16 +682,10 @@ def face_login():
|
|
| 661 |
)
|
| 662 |
|
| 663 |
if result["verified"]:
|
| 664 |
-
#
|
| 665 |
-
|
| 666 |
-
session.permanent = True
|
| 667 |
-
session['logged_in'] = True
|
| 668 |
-
session['user_type'] = face_role
|
| 669 |
-
session[id_field] = user[id_field]
|
| 670 |
-
session['name'] = user.get('name', 'Unknown')
|
| 671 |
-
session['login_time'] = datetime.now().isoformat()
|
| 672 |
|
| 673 |
-
print(f"Face login successful for {user.get('name')},
|
| 674 |
flash('Face login successful!', 'success')
|
| 675 |
|
| 676 |
# Cleanup
|
|
@@ -678,7 +693,11 @@ def face_login():
|
|
| 678 |
if os.path.exists(temp_file):
|
| 679 |
os.remove(temp_file)
|
| 680 |
gc.collect()
|
| 681 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 682 |
|
| 683 |
if os.path.exists(temp_ref_path):
|
| 684 |
os.remove(temp_ref_path)
|
|
@@ -725,11 +744,9 @@ def auto_face_login():
|
|
| 725 |
if face_role == 'teacher':
|
| 726 |
collection = teachers_collection
|
| 727 |
id_field = 'teacher_id'
|
| 728 |
-
dashboard_route = '/teacher_dashboard'
|
| 729 |
else:
|
| 730 |
collection = students_collection
|
| 731 |
id_field = 'student_id'
|
| 732 |
-
dashboard_route = '/dashboard'
|
| 733 |
|
| 734 |
# Use DeepFace for recognition with improved temp file handling
|
| 735 |
temp_auto_path = get_unique_temp_path("auto_login")
|
|
@@ -755,16 +772,10 @@ def auto_face_login():
|
|
| 755 |
)
|
| 756 |
|
| 757 |
if result["verified"]:
|
| 758 |
-
#
|
| 759 |
-
|
| 760 |
-
session.permanent = True
|
| 761 |
-
session['logged_in'] = True
|
| 762 |
-
session['user_type'] = face_role
|
| 763 |
-
session[id_field] = user[id_field]
|
| 764 |
-
session['name'] = user.get('name', 'Unknown')
|
| 765 |
-
session['login_time'] = datetime.now().isoformat()
|
| 766 |
|
| 767 |
-
print(f"Auto face login successful for {user.get('name')},
|
| 768 |
|
| 769 |
# Cleanup
|
| 770 |
for temp_file in [temp_ref_path, temp_auto_path]:
|
|
@@ -772,10 +783,13 @@ def auto_face_login():
|
|
| 772 |
os.remove(temp_file)
|
| 773 |
|
| 774 |
gc.collect()
|
|
|
|
|
|
|
|
|
|
| 775 |
return jsonify({
|
| 776 |
'success': True,
|
| 777 |
'message': f'Welcome {user["name"]}! Redirecting...',
|
| 778 |
-
'redirect_url': dashboard_route,
|
| 779 |
'face_role': face_role
|
| 780 |
})
|
| 781 |
|
|
@@ -801,48 +815,50 @@ def auto_face_login():
|
|
| 801 |
|
| 802 |
@app.route('/attendance.html')
|
| 803 |
def attendance_page():
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
|
|
|
|
|
|
| 807 |
return redirect(url_for('login_page'))
|
| 808 |
-
|
|
|
|
| 809 |
student = students_collection.find_one({'student_id': student_id})
|
| 810 |
-
return render_template('attendance.html', student=student)
|
| 811 |
|
| 812 |
@app.route('/dashboard')
|
| 813 |
def dashboard():
|
| 814 |
-
|
| 815 |
-
print(f"Request headers: {dict(request.headers)}")
|
| 816 |
-
print(f"Request cookies: {dict(request.cookies)}")
|
| 817 |
-
print(f"Session data: {dict(session)}")
|
| 818 |
-
print(f"Session keys: {list(session.keys())}")
|
| 819 |
-
print(f"Session permanent: {getattr(session, 'permanent', 'N/A')}")
|
| 820 |
-
print(f"Session modified: {getattr(session, 'modified', 'N/A')}")
|
| 821 |
-
print(f"User-Agent: {request.headers.get('User-Agent', 'N/A')}")
|
| 822 |
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
user_type = session.get('user_type')
|
| 826 |
-
student_id = session.get('student_id')
|
| 827 |
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
print(f"student_id: {student_id} (type: {type(student_id)})")
|
| 831 |
-
|
| 832 |
-
if not logged_in or user_type != 'student':
|
| 833 |
-
print("=== SESSION CHECK FAILED ===")
|
| 834 |
-
print("Possible causes:")
|
| 835 |
-
print("1. Session cookie not being sent")
|
| 836 |
-
print("2. Session data not persisting")
|
| 837 |
-
print("3. Different user agents between requests")
|
| 838 |
flash('Please log in to access the dashboard.', 'info')
|
| 839 |
return redirect(url_for('login_page'))
|
| 840 |
|
| 841 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
student = students_collection.find_one({'student_id': student_id})
|
| 843 |
if not student:
|
| 844 |
print("Student not found in database")
|
| 845 |
-
|
| 846 |
flash('Student record not found. Please log in again.', 'danger')
|
| 847 |
return redirect(url_for('login_page'))
|
| 848 |
|
|
@@ -856,7 +872,12 @@ def dashboard():
|
|
| 856 |
attendance_records = list(attendance_collection.find({'student_id': student_id}).sort('date', -1))
|
| 857 |
|
| 858 |
print(f"Dashboard loaded successfully for {student.get('name')}")
|
| 859 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
|
| 861 |
except Exception as e:
|
| 862 |
print(f"Dashboard error: {e}")
|
|
@@ -865,11 +886,22 @@ def dashboard():
|
|
| 865 |
|
| 866 |
@app.route('/mark-attendance', methods=['POST'])
|
| 867 |
def mark_attendance():
|
| 868 |
-
if 'logged_in' not in session or session.get('user_type') != 'student':
|
| 869 |
-
return jsonify({'success': False, 'message': 'Not logged in'})
|
| 870 |
-
|
| 871 |
data = request.json
|
| 872 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 873 |
program = data.get('program')
|
| 874 |
semester = data.get('semester')
|
| 875 |
course = data.get('course')
|
|
@@ -1048,10 +1080,13 @@ def mark_attendance():
|
|
| 1048 |
|
| 1049 |
@app.route('/liveness-preview', methods=['POST'])
|
| 1050 |
def liveness_preview():
|
| 1051 |
-
|
| 1052 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
try:
|
| 1054 |
-
data = request.json or {}
|
| 1055 |
face_image = data.get('face_image')
|
| 1056 |
if not face_image:
|
| 1057 |
return jsonify({'success': False, 'message': 'No image received'})
|
|
@@ -1173,18 +1208,12 @@ def teacher_login():
|
|
| 1173 |
print(f"Teacher found: {bool(teacher)}")
|
| 1174 |
|
| 1175 |
if teacher and teacher.get('password') == password:
|
| 1176 |
-
#
|
| 1177 |
-
|
| 1178 |
-
session.permanent = True
|
| 1179 |
-
session['logged_in'] = True
|
| 1180 |
-
session['user_type'] = 'teacher'
|
| 1181 |
-
session['teacher_id'] = teacher_id
|
| 1182 |
-
session['name'] = teacher.get('name', 'Unknown')
|
| 1183 |
-
session['login_time'] = datetime.now().isoformat()
|
| 1184 |
|
| 1185 |
-
print(f"Teacher
|
| 1186 |
flash('Login successful!', 'success')
|
| 1187 |
-
return redirect(url_for('teacher_dashboard'))
|
| 1188 |
else:
|
| 1189 |
print("Invalid teacher credentials")
|
| 1190 |
flash('Invalid credentials. Please try again.', 'danger')
|
|
@@ -1197,21 +1226,31 @@ def teacher_login():
|
|
| 1197 |
|
| 1198 |
@app.route('/teacher_dashboard')
|
| 1199 |
def teacher_dashboard():
|
| 1200 |
-
|
|
|
|
|
|
|
| 1201 |
|
| 1202 |
-
if not
|
| 1203 |
-
print("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1204 |
flash('Please log in to access the teacher dashboard.', 'info')
|
| 1205 |
return redirect(url_for('teacher_login_page'))
|
| 1206 |
|
| 1207 |
try:
|
| 1208 |
-
teacher_id =
|
| 1209 |
print(f"Loading teacher dashboard for teacher: {teacher_id}")
|
| 1210 |
|
| 1211 |
teacher = teachers_collection.find_one({'teacher_id': teacher_id})
|
| 1212 |
if not teacher:
|
| 1213 |
print("Teacher not found in database")
|
| 1214 |
-
|
| 1215 |
flash('Teacher record not found. Please log in again.', 'danger')
|
| 1216 |
return redirect(url_for('teacher_login_page'))
|
| 1217 |
|
|
@@ -1222,7 +1261,7 @@ def teacher_dashboard():
|
|
| 1222 |
teacher['face_image_url'] = f"data:{mime_type};base64,{face_image_base64}"
|
| 1223 |
|
| 1224 |
print(f"Teacher dashboard loaded successfully for {teacher.get('name')}")
|
| 1225 |
-
return render_template('teacher_dashboard.html', teacher=teacher)
|
| 1226 |
|
| 1227 |
except Exception as e:
|
| 1228 |
print(f"Teacher dashboard error: {e}")
|
|
@@ -1231,16 +1270,20 @@ def teacher_dashboard():
|
|
| 1231 |
|
| 1232 |
@app.route('/teacher_logout')
|
| 1233 |
def teacher_logout():
|
| 1234 |
-
|
| 1235 |
-
|
|
|
|
|
|
|
| 1236 |
flash('You have been logged out', 'info')
|
| 1237 |
return redirect(url_for('teacher_login_page'))
|
| 1238 |
|
| 1239 |
# --------- COMMON LOGOUT ---------
|
| 1240 |
@app.route('/logout')
|
| 1241 |
def logout():
|
| 1242 |
-
|
| 1243 |
-
|
|
|
|
|
|
|
| 1244 |
flash('You have been logged out', 'info')
|
| 1245 |
return redirect(url_for('login_page'))
|
| 1246 |
|
|
@@ -1333,37 +1376,37 @@ def health_check():
|
|
| 1333 |
return jsonify({
|
| 1334 |
'status': 'healthy',
|
| 1335 |
'platform': 'hugging_face',
|
| 1336 |
-
'
|
| 1337 |
-
'session_storage': 'built-in_cookies', # Fixed: removed SESSION_TYPE reference
|
| 1338 |
'proxy_fix': 'enabled',
|
| 1339 |
-
'session_keys': list(session.keys()),
|
| 1340 |
'timestamp': datetime.now().isoformat()
|
| 1341 |
}), 200
|
| 1342 |
|
| 1343 |
@app.route('/debug-session')
|
| 1344 |
def debug_session():
|
|
|
|
|
|
|
| 1345 |
return jsonify({
|
| 1346 |
-
'
|
| 1347 |
-
'
|
| 1348 |
-
'
|
| 1349 |
'headers': dict(request.headers),
|
| 1350 |
-
'
|
| 1351 |
-
'
|
| 1352 |
-
'
|
| 1353 |
-
'session_permanent': getattr(session, 'permanent', 'N/A')
|
| 1354 |
})
|
| 1355 |
|
| 1356 |
@app.route('/debug-session-detailed')
|
| 1357 |
def debug_session_detailed():
|
|
|
|
|
|
|
| 1358 |
return jsonify({
|
| 1359 |
-
'
|
| 1360 |
-
'
|
|
|
|
| 1361 |
'cookies_received': dict(request.cookies),
|
| 1362 |
'headers': dict(request.headers),
|
| 1363 |
'user_agent': request.headers.get('User-Agent'),
|
| 1364 |
'remote_addr': request.remote_addr,
|
| 1365 |
-
'session_permanent': getattr(session, 'permanent', 'N/A'),
|
| 1366 |
-
'session_modified': getattr(session, 'modified', 'N/A'),
|
| 1367 |
'flask_secret_key_length': len(app.secret_key),
|
| 1368 |
'session_interface_type': str(type(app.session_interface)),
|
| 1369 |
'timestamp': datetime.now().isoformat()
|
|
@@ -1372,15 +1415,19 @@ def debug_session_detailed():
|
|
| 1372 |
@app.route('/test-session')
|
| 1373 |
def test_session():
|
| 1374 |
"""Test session functionality"""
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1379 |
|
| 1380 |
return jsonify({
|
| 1381 |
-
'message': '
|
| 1382 |
-
'
|
| 1383 |
-
'test_successful':
|
| 1384 |
})
|
| 1385 |
|
| 1386 |
@app.route('/cleanup', methods=['POST'])
|
|
@@ -1395,5 +1442,5 @@ def manual_cleanup():
|
|
| 1395 |
# MAIN APPLICATION ENTRY POINT
|
| 1396 |
if __name__ == '__main__':
|
| 1397 |
port = int(os.environ.get('PORT', 7860)) # Hugging Face uses port 7860
|
| 1398 |
-
print(f"Starting Flask app on port {port} with
|
| 1399 |
app.run(host='0.0.0.0', port=port, debug=False)
|
|
|
|
| 5 |
import logging
|
| 6 |
import time
|
| 7 |
import uuid
|
| 8 |
+
import secrets # Added for token generation
|
| 9 |
import pymongo
|
| 10 |
from pymongo import MongoClient
|
| 11 |
from bson.binary import Binary
|
|
|
|
| 68 |
x_prefix=0
|
| 69 |
)
|
| 70 |
|
| 71 |
+
print("Flask app initialized with ProxyFix middleware and token-based sessions")
|
| 72 |
|
| 73 |
# Create temporary directory for image processing
|
| 74 |
TEMP_DIR = tempfile.mkdtemp()
|
|
|
|
| 99 |
teachers_collection = db['teachers']
|
| 100 |
attendance_collection = db['attendance']
|
| 101 |
metrics_events = db['metrics_events']
|
| 102 |
+
sessions_collection = db['user_sessions'] # Added for token-based sessions
|
| 103 |
|
| 104 |
# Create indexes for better performance
|
| 105 |
students_collection.create_index([("student_id", pymongo.ASCENDING)], unique=True)
|
|
|
|
| 112 |
metrics_events.create_index([("ts", pymongo.DESCENDING)])
|
| 113 |
metrics_events.create_index([("event", pymongo.ASCENDING)])
|
| 114 |
metrics_events.create_index([("attempt_type", pymongo.ASCENDING)])
|
| 115 |
+
sessions_collection.create_index([("token", pymongo.ASCENDING)], unique=True)
|
| 116 |
+
sessions_collection.create_index([("expires_at", pymongo.ASCENDING)], expireAfterSeconds=0)
|
| 117 |
print("MongoDB connection successful")
|
| 118 |
except Exception as e:
|
| 119 |
print(f"MongoDB connection error: {e}")
|
|
|
|
| 349 |
"""Legacy wrapper for the new DeepFace recognition"""
|
| 350 |
return recognize_face_deepface(image, user_id, user_type)
|
| 351 |
|
| 352 |
+
# Token-based session helpers
|
| 353 |
+
def validate_session_token(token):
|
| 354 |
+
"""Validate session token and return session data"""
|
| 355 |
+
if not token:
|
| 356 |
+
return None
|
| 357 |
+
|
| 358 |
+
session_data = sessions_collection.find_one({'token': token})
|
| 359 |
+
if not session_data:
|
| 360 |
+
return None
|
| 361 |
+
|
| 362 |
+
# Check if session expired
|
| 363 |
+
if datetime.now() > session_data.get('expires_at', datetime.now()):
|
| 364 |
+
sessions_collection.delete_one({'token': token})
|
| 365 |
+
return None
|
| 366 |
+
|
| 367 |
+
return session_data
|
| 368 |
+
|
| 369 |
+
def create_session_token(user_id, user_type):
|
| 370 |
+
"""Create new session token"""
|
| 371 |
+
token = secrets.token_urlsafe(32)
|
| 372 |
+
session_data = {
|
| 373 |
+
'token': token,
|
| 374 |
+
'user_id': user_id,
|
| 375 |
+
'user_type': user_type,
|
| 376 |
+
'created_at': datetime.now(),
|
| 377 |
+
'expires_at': datetime.now() + timedelta(hours=2)
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
# Clear old sessions for this user
|
| 381 |
+
if user_type == 'student':
|
| 382 |
+
sessions_collection.delete_many({'student_id': user_id})
|
| 383 |
+
session_data['student_id'] = user_id
|
| 384 |
+
else:
|
| 385 |
+
sessions_collection.delete_many({'teacher_id': user_id})
|
| 386 |
+
session_data['teacher_id'] = user_id
|
| 387 |
+
|
| 388 |
+
sessions_collection.insert_one(session_data)
|
| 389 |
+
return token
|
| 390 |
+
|
| 391 |
# ---------------------- Metrics helpers ----------------------
|
| 392 |
def log_metrics_event(event: dict):
|
| 393 |
try:
|
|
|
|
| 601 |
student_id = request.form.get('student_id')
|
| 602 |
password = request.form.get('password')
|
| 603 |
|
| 604 |
+
print(f"=== TOKEN-BASED LOGIN DEBUG ===")
|
| 605 |
print(f"Student ID: {student_id}")
|
|
|
|
|
|
|
| 606 |
|
| 607 |
if not student_id or not password:
|
| 608 |
flash('Student ID and password are required.', 'danger')
|
|
|
|
| 612 |
print(f"Student found: {bool(student)}")
|
| 613 |
|
| 614 |
if student and student.get('password') == password:
|
| 615 |
+
# Create session token
|
| 616 |
+
token = create_session_token(student_id, 'student')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
|
| 618 |
+
print(f"Session token created: {token[:10]}...")
|
| 619 |
flash('Login successful!', 'success')
|
| 620 |
|
| 621 |
+
# Redirect with token in URL
|
| 622 |
+
return redirect(url_for('dashboard', token=token))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
else:
|
| 624 |
print("Invalid credentials")
|
| 625 |
flash('Invalid credentials. Please try again.', 'danger')
|
|
|
|
| 682 |
)
|
| 683 |
|
| 684 |
if result["verified"]:
|
| 685 |
+
# Create session token
|
| 686 |
+
token = create_session_token(user[id_field], face_role)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 687 |
|
| 688 |
+
print(f"Face login successful for {user.get('name')}, Token: {token[:10]}...")
|
| 689 |
flash('Face login successful!', 'success')
|
| 690 |
|
| 691 |
# Cleanup
|
|
|
|
| 693 |
if os.path.exists(temp_file):
|
| 694 |
os.remove(temp_file)
|
| 695 |
gc.collect()
|
| 696 |
+
|
| 697 |
+
if face_role == 'student':
|
| 698 |
+
return redirect(url_for(dashboard_route, token=token))
|
| 699 |
+
else:
|
| 700 |
+
return redirect(url_for(dashboard_route, token=token))
|
| 701 |
|
| 702 |
if os.path.exists(temp_ref_path):
|
| 703 |
os.remove(temp_ref_path)
|
|
|
|
| 744 |
if face_role == 'teacher':
|
| 745 |
collection = teachers_collection
|
| 746 |
id_field = 'teacher_id'
|
|
|
|
| 747 |
else:
|
| 748 |
collection = students_collection
|
| 749 |
id_field = 'student_id'
|
|
|
|
| 750 |
|
| 751 |
# Use DeepFace for recognition with improved temp file handling
|
| 752 |
temp_auto_path = get_unique_temp_path("auto_login")
|
|
|
|
| 772 |
)
|
| 773 |
|
| 774 |
if result["verified"]:
|
| 775 |
+
# Create session token
|
| 776 |
+
token = create_session_token(user[id_field], face_role)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 777 |
|
| 778 |
+
print(f"Auto face login successful for {user.get('name')}, Token: {token[:10]}...")
|
| 779 |
|
| 780 |
# Cleanup
|
| 781 |
for temp_file in [temp_ref_path, temp_auto_path]:
|
|
|
|
| 783 |
os.remove(temp_file)
|
| 784 |
|
| 785 |
gc.collect()
|
| 786 |
+
|
| 787 |
+
dashboard_route = '/dashboard' if face_role == 'student' else '/teacher_dashboard'
|
| 788 |
+
|
| 789 |
return jsonify({
|
| 790 |
'success': True,
|
| 791 |
'message': f'Welcome {user["name"]}! Redirecting...',
|
| 792 |
+
'redirect_url': f'{dashboard_route}?token={token}',
|
| 793 |
'face_role': face_role
|
| 794 |
})
|
| 795 |
|
|
|
|
| 815 |
|
| 816 |
@app.route('/attendance.html')
|
| 817 |
def attendance_page():
|
| 818 |
+
token = request.args.get('token')
|
| 819 |
+
session_data = validate_session_token(token)
|
| 820 |
+
|
| 821 |
+
if not session_data or session_data.get('user_type') != 'student':
|
| 822 |
+
print("Token validation failed for attendance page")
|
| 823 |
return redirect(url_for('login_page'))
|
| 824 |
+
|
| 825 |
+
student_id = session_data.get('student_id')
|
| 826 |
student = students_collection.find_one({'student_id': student_id})
|
| 827 |
+
return render_template('attendance.html', student=student, session_token=token)
|
| 828 |
|
| 829 |
@app.route('/dashboard')
|
| 830 |
def dashboard():
|
| 831 |
+
token = request.args.get('token')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
+
print(f"=== TOKEN-BASED DASHBOARD DEBUG ===")
|
| 834 |
+
print(f"Token received: {token[:10] if token else 'None'}...")
|
|
|
|
|
|
|
| 835 |
|
| 836 |
+
if not token:
|
| 837 |
+
print("No token provided")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 838 |
flash('Please log in to access the dashboard.', 'info')
|
| 839 |
return redirect(url_for('login_page'))
|
| 840 |
|
| 841 |
try:
|
| 842 |
+
# Validate session token
|
| 843 |
+
session_data = validate_session_token(token)
|
| 844 |
+
|
| 845 |
+
if not session_data:
|
| 846 |
+
print("Invalid or expired token")
|
| 847 |
+
flash('Session expired. Please log in again.', 'info')
|
| 848 |
+
return redirect(url_for('login_page'))
|
| 849 |
+
|
| 850 |
+
if session_data.get('user_type') != 'student':
|
| 851 |
+
print("Invalid user type")
|
| 852 |
+
flash('Please log in as a student.', 'info')
|
| 853 |
+
return redirect(url_for('login_page'))
|
| 854 |
+
|
| 855 |
+
student_id = session_data.get('student_id')
|
| 856 |
+
print(f"Loading dashboard for student: {student_id}")
|
| 857 |
+
|
| 858 |
student = students_collection.find_one({'student_id': student_id})
|
| 859 |
if not student:
|
| 860 |
print("Student not found in database")
|
| 861 |
+
sessions_collection.delete_one({'token': token})
|
| 862 |
flash('Student record not found. Please log in again.', 'danger')
|
| 863 |
return redirect(url_for('login_page'))
|
| 864 |
|
|
|
|
| 872 |
attendance_records = list(attendance_collection.find({'student_id': student_id}).sort('date', -1))
|
| 873 |
|
| 874 |
print(f"Dashboard loaded successfully for {student.get('name')}")
|
| 875 |
+
|
| 876 |
+
# Pass token to template for subsequent requests
|
| 877 |
+
return render_template('dashboard.html',
|
| 878 |
+
student=student,
|
| 879 |
+
attendance_records=attendance_records,
|
| 880 |
+
session_token=token)
|
| 881 |
|
| 882 |
except Exception as e:
|
| 883 |
print(f"Dashboard error: {e}")
|
|
|
|
| 886 |
|
| 887 |
@app.route('/mark-attendance', methods=['POST'])
|
| 888 |
def mark_attendance():
|
|
|
|
|
|
|
|
|
|
| 889 |
data = request.json
|
| 890 |
+
token = data.get('session_token')
|
| 891 |
+
|
| 892 |
+
if not token:
|
| 893 |
+
return jsonify({'success': False, 'message': 'Not authenticated'})
|
| 894 |
+
|
| 895 |
+
# Validate token
|
| 896 |
+
session_data = validate_session_token(token)
|
| 897 |
+
|
| 898 |
+
if not session_data:
|
| 899 |
+
return jsonify({'success': False, 'message': 'Session expired'})
|
| 900 |
+
|
| 901 |
+
if session_data.get('user_type') != 'student':
|
| 902 |
+
return jsonify({'success': False, 'message': 'Invalid user type'})
|
| 903 |
+
|
| 904 |
+
student_id = session_data.get('student_id')
|
| 905 |
program = data.get('program')
|
| 906 |
semester = data.get('semester')
|
| 907 |
course = data.get('course')
|
|
|
|
| 1080 |
|
| 1081 |
@app.route('/liveness-preview', methods=['POST'])
|
| 1082 |
def liveness_preview():
|
| 1083 |
+
data = request.json or {}
|
| 1084 |
+
token = data.get('session_token')
|
| 1085 |
+
|
| 1086 |
+
if not token or not validate_session_token(token):
|
| 1087 |
+
return jsonify({'success': False, 'message': 'Not authenticated'})
|
| 1088 |
+
|
| 1089 |
try:
|
|
|
|
| 1090 |
face_image = data.get('face_image')
|
| 1091 |
if not face_image:
|
| 1092 |
return jsonify({'success': False, 'message': 'No image received'})
|
|
|
|
| 1208 |
print(f"Teacher found: {bool(teacher)}")
|
| 1209 |
|
| 1210 |
if teacher and teacher.get('password') == password:
|
| 1211 |
+
# Create session token
|
| 1212 |
+
token = create_session_token(teacher_id, 'teacher')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1213 |
|
| 1214 |
+
print(f"Teacher token created: {token[:10]}...")
|
| 1215 |
flash('Login successful!', 'success')
|
| 1216 |
+
return redirect(url_for('teacher_dashboard', token=token))
|
| 1217 |
else:
|
| 1218 |
print("Invalid teacher credentials")
|
| 1219 |
flash('Invalid credentials. Please try again.', 'danger')
|
|
|
|
| 1226 |
|
| 1227 |
@app.route('/teacher_dashboard')
|
| 1228 |
def teacher_dashboard():
|
| 1229 |
+
token = request.args.get('token')
|
| 1230 |
+
|
| 1231 |
+
print(f"Teacher dashboard access attempt. Token: {token[:10] if token else 'None'}...")
|
| 1232 |
|
| 1233 |
+
if not token:
|
| 1234 |
+
print("No token provided for teacher dashboard")
|
| 1235 |
+
flash('Please log in to access the teacher dashboard.', 'info')
|
| 1236 |
+
return redirect(url_for('teacher_login_page'))
|
| 1237 |
+
|
| 1238 |
+
# Validate session token
|
| 1239 |
+
session_data = validate_session_token(token)
|
| 1240 |
+
|
| 1241 |
+
if not session_data or session_data.get('user_type') != 'teacher':
|
| 1242 |
+
print("Teacher token validation failed - redirecting to login")
|
| 1243 |
flash('Please log in to access the teacher dashboard.', 'info')
|
| 1244 |
return redirect(url_for('teacher_login_page'))
|
| 1245 |
|
| 1246 |
try:
|
| 1247 |
+
teacher_id = session_data.get('teacher_id')
|
| 1248 |
print(f"Loading teacher dashboard for teacher: {teacher_id}")
|
| 1249 |
|
| 1250 |
teacher = teachers_collection.find_one({'teacher_id': teacher_id})
|
| 1251 |
if not teacher:
|
| 1252 |
print("Teacher not found in database")
|
| 1253 |
+
sessions_collection.delete_one({'token': token})
|
| 1254 |
flash('Teacher record not found. Please log in again.', 'danger')
|
| 1255 |
return redirect(url_for('teacher_login_page'))
|
| 1256 |
|
|
|
|
| 1261 |
teacher['face_image_url'] = f"data:{mime_type};base64,{face_image_base64}"
|
| 1262 |
|
| 1263 |
print(f"Teacher dashboard loaded successfully for {teacher.get('name')}")
|
| 1264 |
+
return render_template('teacher_dashboard.html', teacher=teacher, session_token=token)
|
| 1265 |
|
| 1266 |
except Exception as e:
|
| 1267 |
print(f"Teacher dashboard error: {e}")
|
|
|
|
| 1270 |
|
| 1271 |
@app.route('/teacher_logout')
|
| 1272 |
def teacher_logout():
|
| 1273 |
+
token = request.args.get('token')
|
| 1274 |
+
if token:
|
| 1275 |
+
sessions_collection.delete_one({'token': token})
|
| 1276 |
+
print(f"Teacher token {token[:10]}... invalidated")
|
| 1277 |
flash('You have been logged out', 'info')
|
| 1278 |
return redirect(url_for('teacher_login_page'))
|
| 1279 |
|
| 1280 |
# --------- COMMON LOGOUT ---------
|
| 1281 |
@app.route('/logout')
|
| 1282 |
def logout():
|
| 1283 |
+
token = request.args.get('token')
|
| 1284 |
+
if token:
|
| 1285 |
+
sessions_collection.delete_one({'token': token})
|
| 1286 |
+
print(f"Token {token[:10]}... invalidated")
|
| 1287 |
flash('You have been logged out', 'info')
|
| 1288 |
return redirect(url_for('login_page'))
|
| 1289 |
|
|
|
|
| 1376 |
return jsonify({
|
| 1377 |
'status': 'healthy',
|
| 1378 |
'platform': 'hugging_face',
|
| 1379 |
+
'session_type': 'token_based',
|
|
|
|
| 1380 |
'proxy_fix': 'enabled',
|
|
|
|
| 1381 |
'timestamp': datetime.now().isoformat()
|
| 1382 |
}), 200
|
| 1383 |
|
| 1384 |
@app.route('/debug-session')
|
| 1385 |
def debug_session():
|
| 1386 |
+
token = request.args.get('token')
|
| 1387 |
+
session_data = validate_session_token(token) if token else None
|
| 1388 |
return jsonify({
|
| 1389 |
+
'token_provided': bool(token),
|
| 1390 |
+
'session_valid': bool(session_data),
|
| 1391 |
+
'session_data': session_data if session_data else None,
|
| 1392 |
'headers': dict(request.headers),
|
| 1393 |
+
'cookies': dict(request.cookies),
|
| 1394 |
+
'session_type': 'token_based',
|
| 1395 |
+
'proxy_fix': 'enabled'
|
|
|
|
| 1396 |
})
|
| 1397 |
|
| 1398 |
@app.route('/debug-session-detailed')
|
| 1399 |
def debug_session_detailed():
|
| 1400 |
+
token = request.args.get('token')
|
| 1401 |
+
session_data = validate_session_token(token) if token else None
|
| 1402 |
return jsonify({
|
| 1403 |
+
'token_provided': bool(token),
|
| 1404 |
+
'token_valid': bool(session_data),
|
| 1405 |
+
'session_data': session_data,
|
| 1406 |
'cookies_received': dict(request.cookies),
|
| 1407 |
'headers': dict(request.headers),
|
| 1408 |
'user_agent': request.headers.get('User-Agent'),
|
| 1409 |
'remote_addr': request.remote_addr,
|
|
|
|
|
|
|
| 1410 |
'flask_secret_key_length': len(app.secret_key),
|
| 1411 |
'session_interface_type': str(type(app.session_interface)),
|
| 1412 |
'timestamp': datetime.now().isoformat()
|
|
|
|
| 1415 |
@app.route('/test-session')
|
| 1416 |
def test_session():
|
| 1417 |
"""Test session functionality"""
|
| 1418 |
+
token = secrets.token_urlsafe(16)
|
| 1419 |
+
test_session = {
|
| 1420 |
+
'token': token,
|
| 1421 |
+
'test_data': 'working',
|
| 1422 |
+
'timestamp': datetime.now(),
|
| 1423 |
+
'expires_at': datetime.now() + timedelta(minutes=5)
|
| 1424 |
+
}
|
| 1425 |
+
sessions_collection.insert_one(test_session)
|
| 1426 |
|
| 1427 |
return jsonify({
|
| 1428 |
+
'message': 'Token-based session test completed',
|
| 1429 |
+
'test_token': token,
|
| 1430 |
+
'test_successful': True
|
| 1431 |
})
|
| 1432 |
|
| 1433 |
@app.route('/cleanup', methods=['POST'])
|
|
|
|
| 1442 |
# MAIN APPLICATION ENTRY POINT
|
| 1443 |
if __name__ == '__main__':
|
| 1444 |
port = int(os.environ.get('PORT', 7860)) # Hugging Face uses port 7860
|
| 1445 |
+
print(f"Starting Flask app on port {port} with token-based authentication")
|
| 1446 |
app.run(host='0.0.0.0', port=port, debug=False)
|
app/templates/dashboard.html
CHANGED
|
@@ -303,19 +303,19 @@
|
|
| 303 |
</style>
|
| 304 |
</head>
|
| 305 |
<body>
|
| 306 |
-
|
| 307 |
<header class="header">
|
| 308 |
<div class="container header-content">
|
| 309 |
<a href="/" class="logo"><i class="fas fa-user-check"></i> Face Attendance System</a>
|
| 310 |
<ul class="nav-links">
|
| 311 |
-
<li><a href="
|
| 312 |
<li><a href="/logout">Logout</a></li>
|
| 313 |
</ul>
|
| 314 |
<button class="mobile-menu-btn"><i class="fas fa-bars"></i></button>
|
| 315 |
</div>
|
| 316 |
</header>
|
| 317 |
|
| 318 |
-
|
| 319 |
<main class="main-content">
|
| 320 |
<div class="container">
|
| 321 |
<div class="dashboard-card">
|
|
@@ -323,7 +323,7 @@
|
|
| 323 |
<h2><i class="fas fa-tachometer-alt"></i> Student Dashboard</h2>
|
| 324 |
</div>
|
| 325 |
<div class="card-body">
|
| 326 |
-
|
| 327 |
{% with messages = get_flashed_messages(with_categories=true) %}
|
| 328 |
{% if messages %}
|
| 329 |
{% for category, message in messages %}
|
|
@@ -360,7 +360,7 @@
|
|
| 360 |
</div>
|
| 361 |
</div>
|
| 362 |
|
| 363 |
-
|
| 364 |
<div class="attendance-action-area">
|
| 365 |
<h3 class="attendance-action-title"><i class="fas fa-clipboard-check"></i> Attendance Management</h3>
|
| 366 |
|
|
@@ -397,16 +397,16 @@
|
|
| 397 |
</div>
|
| 398 |
</div>
|
| 399 |
|
| 400 |
-
|
| 401 |
<div class="attendance-dropdown-container" id="faceRecognitionContainer">
|
| 402 |
<div class="camera-container">
|
| 403 |
<div class="camera-wrapper" style="position: relative; display: inline-block; width: 100%;">
|
| 404 |
<video id="faceVideo" class="camera-feed" autoplay playsinline></video>
|
| 405 |
-
|
| 406 |
<img id="livenessOverlayImg" class="overlay-img" alt="Live liveness overlay"/>
|
| 407 |
-
|
| 408 |
<canvas id="faceCanvas" class="d-none"></canvas>
|
| 409 |
-
|
| 410 |
<div id="faceOverlay" class="camera-overlay d-none">
|
| 411 |
<span id="recognitionStatus">Recognizing...</span>
|
| 412 |
</div>
|
|
@@ -470,6 +470,15 @@
|
|
| 470 |
</main>
|
| 471 |
|
| 472 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
// Mobile menu toggle
|
| 474 |
document.addEventListener('DOMContentLoaded', function () {
|
| 475 |
const mobileMenuBtn = document.querySelector('.mobile-menu-btn');
|
|
@@ -551,11 +560,16 @@
|
|
| 551 |
previewCanvas.height = faceVideo.videoHeight || 300;
|
| 552 |
previewCtx.drawImage(faceVideo, 0, 0, previewCanvas.width, previewCanvas.height);
|
| 553 |
const frameDataUrl = previewCanvas.toDataURL('image/jpeg', 0.6);
|
|
|
|
| 554 |
const resp = await fetch('/liveness-preview', {
|
| 555 |
method: 'POST',
|
| 556 |
headers: { 'Content-Type': 'application/json' },
|
| 557 |
-
body: JSON.stringify({
|
|
|
|
|
|
|
|
|
|
| 558 |
});
|
|
|
|
| 559 |
const data = await resp.json();
|
| 560 |
if (data && data.overlay && livenessOverlayImg) {
|
| 561 |
livenessOverlayImg.src = data.overlay;
|
|
@@ -661,7 +675,8 @@
|
|
| 661 |
program: programSelect.value,
|
| 662 |
semester: semesterSelect.value,
|
| 663 |
course: courseSelect.value,
|
| 664 |
-
face_image: imageData
|
|
|
|
| 665 |
};
|
| 666 |
|
| 667 |
fetch('/mark-attendance', {
|
|
@@ -669,8 +684,18 @@
|
|
| 669 |
headers: { 'Content-Type': 'application/json' },
|
| 670 |
body: JSON.stringify(attendanceData)
|
| 671 |
})
|
| 672 |
-
.then(response =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
.then(data => {
|
|
|
|
|
|
|
| 674 |
// Show final server overlay from attendance endpoint (bbox + label)
|
| 675 |
if (data.overlay && livenessOverlayImg) {
|
| 676 |
livenessOverlayImg.src = data.overlay;
|
|
@@ -690,7 +715,9 @@
|
|
| 690 |
if (data.success) {
|
| 691 |
recognitionStatus.textContent = 'Attendance marked successfully';
|
| 692 |
recognitionStatus.style.color = '#10b981';
|
| 693 |
-
setTimeout(() => {
|
|
|
|
|
|
|
| 694 |
} else {
|
| 695 |
// Other failures (e.g., face not recognized)
|
| 696 |
recognitionStatus.textContent = data.message || 'Face not recognized. Please try again.';
|
|
@@ -737,4 +764,4 @@
|
|
| 737 |
});
|
| 738 |
</script>
|
| 739 |
</body>
|
| 740 |
-
</html>
|
|
|
|
| 303 |
</style>
|
| 304 |
</head>
|
| 305 |
<body>
|
| 306 |
+
<!-- Header -->
|
| 307 |
<header class="header">
|
| 308 |
<div class="container header-content">
|
| 309 |
<a href="/" class="logo"><i class="fas fa-user-check"></i> Face Attendance System</a>
|
| 310 |
<ul class="nav-links">
|
| 311 |
+
<li><a href="{{ url_for('dashboard', token=session_token) }}">Dashboard</a></li>
|
| 312 |
<li><a href="/logout">Logout</a></li>
|
| 313 |
</ul>
|
| 314 |
<button class="mobile-menu-btn"><i class="fas fa-bars"></i></button>
|
| 315 |
</div>
|
| 316 |
</header>
|
| 317 |
|
| 318 |
+
<!-- Main Content -->
|
| 319 |
<main class="main-content">
|
| 320 |
<div class="container">
|
| 321 |
<div class="dashboard-card">
|
|
|
|
| 323 |
<h2><i class="fas fa-tachometer-alt"></i> Student Dashboard</h2>
|
| 324 |
</div>
|
| 325 |
<div class="card-body">
|
| 326 |
+
<!-- Flash Messages -->
|
| 327 |
{% with messages = get_flashed_messages(with_categories=true) %}
|
| 328 |
{% if messages %}
|
| 329 |
{% for category, message in messages %}
|
|
|
|
| 360 |
</div>
|
| 361 |
</div>
|
| 362 |
|
| 363 |
+
<!-- Mark Attendance Section -->
|
| 364 |
<div class="attendance-action-area">
|
| 365 |
<h3 class="attendance-action-title"><i class="fas fa-clipboard-check"></i> Attendance Management</h3>
|
| 366 |
|
|
|
|
| 397 |
</div>
|
| 398 |
</div>
|
| 399 |
|
| 400 |
+
<!-- Face Recognition Container -->
|
| 401 |
<div class="attendance-dropdown-container" id="faceRecognitionContainer">
|
| 402 |
<div class="camera-container">
|
| 403 |
<div class="camera-wrapper" style="position: relative; display: inline-block; width: 100%;">
|
| 404 |
<video id="faceVideo" class="camera-feed" autoplay playsinline></video>
|
| 405 |
+
<!-- Server-rendered live overlay (YOLO + liveness) -->
|
| 406 |
<img id="livenessOverlayImg" class="overlay-img" alt="Live liveness overlay"/>
|
| 407 |
+
<!-- Hidden capture canvas -->
|
| 408 |
<canvas id="faceCanvas" class="d-none"></canvas>
|
| 409 |
+
<!-- Status overlay (visible during submit) -->
|
| 410 |
<div id="faceOverlay" class="camera-overlay d-none">
|
| 411 |
<span id="recognitionStatus">Recognizing...</span>
|
| 412 |
</div>
|
|
|
|
| 470 |
</main>
|
| 471 |
|
| 472 |
<script>
|
| 473 |
+
// CRITICAL: Store session token for API calls
|
| 474 |
+
const sessionToken = "{{ session_token|default('') }}";
|
| 475 |
+
|
| 476 |
+
// Redirect to login if no session token
|
| 477 |
+
if (!sessionToken) {
|
| 478 |
+
alert('Session expired. Please log in again.');
|
| 479 |
+
window.location.href = '/login.html';
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
// Mobile menu toggle
|
| 483 |
document.addEventListener('DOMContentLoaded', function () {
|
| 484 |
const mobileMenuBtn = document.querySelector('.mobile-menu-btn');
|
|
|
|
| 560 |
previewCanvas.height = faceVideo.videoHeight || 300;
|
| 561 |
previewCtx.drawImage(faceVideo, 0, 0, previewCanvas.width, previewCanvas.height);
|
| 562 |
const frameDataUrl = previewCanvas.toDataURL('image/jpeg', 0.6);
|
| 563 |
+
|
| 564 |
const resp = await fetch('/liveness-preview', {
|
| 565 |
method: 'POST',
|
| 566 |
headers: { 'Content-Type': 'application/json' },
|
| 567 |
+
body: JSON.stringify({
|
| 568 |
+
face_image: frameDataUrl,
|
| 569 |
+
session_token: sessionToken // CRITICAL: Include session token
|
| 570 |
+
})
|
| 571 |
});
|
| 572 |
+
|
| 573 |
const data = await resp.json();
|
| 574 |
if (data && data.overlay && livenessOverlayImg) {
|
| 575 |
livenessOverlayImg.src = data.overlay;
|
|
|
|
| 675 |
program: programSelect.value,
|
| 676 |
semester: semesterSelect.value,
|
| 677 |
course: courseSelect.value,
|
| 678 |
+
face_image: imageData,
|
| 679 |
+
session_token: sessionToken // CRITICAL: Include session token
|
| 680 |
};
|
| 681 |
|
| 682 |
fetch('/mark-attendance', {
|
|
|
|
| 684 |
headers: { 'Content-Type': 'application/json' },
|
| 685 |
body: JSON.stringify(attendanceData)
|
| 686 |
})
|
| 687 |
+
.then(response => {
|
| 688 |
+
// Check if session expired
|
| 689 |
+
if (response.status === 401 || response.status === 403) {
|
| 690 |
+
alert('Session expired. Please log in again.');
|
| 691 |
+
window.location.href = '/login.html';
|
| 692 |
+
return;
|
| 693 |
+
}
|
| 694 |
+
return response.json();
|
| 695 |
+
})
|
| 696 |
.then(data => {
|
| 697 |
+
if (!data) return; // Handle session redirect case
|
| 698 |
+
|
| 699 |
// Show final server overlay from attendance endpoint (bbox + label)
|
| 700 |
if (data.overlay && livenessOverlayImg) {
|
| 701 |
livenessOverlayImg.src = data.overlay;
|
|
|
|
| 715 |
if (data.success) {
|
| 716 |
recognitionStatus.textContent = 'Attendance marked successfully';
|
| 717 |
recognitionStatus.style.color = '#10b981';
|
| 718 |
+
setTimeout(() => {
|
| 719 |
+
window.location.href = `{{ url_for('dashboard') }}?token=${sessionToken}`;
|
| 720 |
+
}, 2000);
|
| 721 |
} else {
|
| 722 |
// Other failures (e.g., face not recognized)
|
| 723 |
recognitionStatus.textContent = data.message || 'Face not recognized. Please try again.';
|
|
|
|
| 764 |
});
|
| 765 |
</script>
|
| 766 |
</body>
|
| 767 |
+
</html>
|