kkt-2002 commited on
Commit
4dcddf6
·
1 Parent(s): b2b104d

Complete token-based authentication system

Browse files
Files changed (2) hide show
  1. app.py +176 -129
  2. 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 built-in cookie sessions")
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
- # CRITICAL FIX: Clear and set session with permanent flag
575
- session.clear()
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
- # Create response and explicitly save session
591
- response = redirect(url_for('dashboard'))
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
- # CRITICAL FIX: Simplified session setting
665
- session.clear()
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')}, Session: {dict(session)}")
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
- return redirect(url_for(dashboard_route))
 
 
 
 
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
- # Clear any existing session
759
- session.clear()
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')}, Session: {dict(session)}")
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
- print(f"Attendance page access. Session: {dict(session)}")
805
- if 'logged_in' not in session or session.get('user_type') != 'student':
806
- print("Session check failed for attendance page")
 
 
807
  return redirect(url_for('login_page'))
808
- student_id = session.get('student_id')
 
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
- print(f"=== DASHBOARD DEBUG ===")
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
- # Check each session key individually
824
- logged_in = session.get('logged_in')
825
- user_type = session.get('user_type')
826
- student_id = session.get('student_id')
827
 
828
- print(f"logged_in: {logged_in} (type: {type(logged_in)})")
829
- print(f"user_type: {user_type} (type: {type(user_type)})")
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
- session.clear()
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
- return render_template('dashboard.html', student=student, attendance_records=attendance_records)
 
 
 
 
 
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
- student_id = session.get('student_id') or data.get('student_id')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- if 'logged_in' not in session or session.get('user_type') != 'student':
1052
- return jsonify({'success': False, 'message': 'Not logged in'})
 
 
 
 
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
- # Clear any existing session
1177
- session.clear()
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 session set: {dict(session)}")
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
- print(f"Teacher dashboard access attempt. Session: {dict(session)}")
 
 
1201
 
1202
- if not session.get('logged_in') or session.get('user_type') != 'teacher':
1203
- print("Teacher session check failed - redirecting to login")
 
 
 
 
 
 
 
 
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 = session.get('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
- session.clear()
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
- print(f"Teacher logout. Session before clear: {dict(session)}")
1235
- session.clear()
 
 
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
- print(f"Student logout. Session before clear: {dict(session)}")
1243
- session.clear()
 
 
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
- 'session_working': bool(session.get('logged_in')),
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
- 'session_data': dict(session),
1347
- 'session_keys': list(session.keys()),
1348
- 'cookies': dict(request.cookies),
1349
  'headers': dict(request.headers),
1350
- 'session_storage': 'built-in_cookies',
1351
- 'proxy_fix': 'enabled',
1352
- 'session_modified': getattr(session, 'modified', 'N/A'),
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
- 'session_data': dict(session),
1360
- 'session_keys': list(session.keys()),
 
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
- session.clear()
1376
- session['test_data'] = 'working'
1377
- session['timestamp'] = datetime.now().isoformat()
1378
- session.permanent = True
 
 
 
 
1379
 
1380
  return jsonify({
1381
- 'message': 'Session test completed',
1382
- 'session_data': dict(session),
1383
- 'test_successful': bool(session.get('test_data'))
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 ProxyFix middleware and built-in sessions")
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
- 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="/dashboard">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,7 +323,7 @@
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,7 +360,7 @@
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,16 +397,16 @@
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,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({ face_image: frameDataUrl })
 
 
 
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 => response.json())
 
 
 
 
 
 
 
 
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(() => { window.location.reload(); }, 2000);
 
 
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>