@@ -44,80 +44,82 @@ def login() -> Any:
4444 """
4545 Handle user login with real authentication.
4646
47- GET: Display login form
48- POST: Process login credentials (validates against database )
47+ GET: Redirect to React admin interface (which handles login UI)
48+ POST: Process login credentials (used by unit tests for session setup )
4949
5050 Returns:
51- Response: Login form template or redirect to dashboard
51+ Response: Redirect to /admin/ for GET, or process login for POST
5252 """
53- if request .method == "POST" :
54- username = request .form .get ("username" , "" ).strip ()
55- password = request .form .get ("password" , "" )
56-
57- # Basic validation
58- if not username :
59- flash ("Username is required" , "error" )
60- return render_template ("auth/login.html" )
61-
62- if not password :
63- flash ("Password is required" , "error" )
64- return render_template ("auth/login.html" )
65-
66- # Real authentication - validate credentials
67- try :
68- user = _authenticate_user (username , password )
69- except RuntimeError as e :
70- # Database initialization error
71- error_message = str (e )
72- flash (error_message , "error" )
73- return render_template ("auth/login.html" )
74-
75- if user is None :
76- # Authentication failed - invalid credentials
77- flash ("Invalid username or password" , "error" )
78- return render_template ("auth/login.html" )
79-
80- if user == "inactive" :
81- # User account is inactive
82- flash ("Account is inactive. Please contact an administrator." , "error" )
83- return render_template ("auth/login.html" )
84-
85- # At this point, user must be a User object (not None or "inactive")
86- # Type narrowing: cast to User for mypy
87- authenticated_user = cast (User , user )
88-
89- # Authentication successful
90- authenticated_user .last_login_at = datetime .now (timezone .utc )
91- db .session .commit ()
92-
93- # Set up session
94- session ["user_id" ] = authenticated_user .id
95- session ["username" ] = authenticated_user .username
96- session ["is_admin" ] = authenticated_user .is_admin
53+ # GET requests redirect to React login
54+ if request .method == "GET" :
55+ return redirect (url_for ("serve_admin" ))
9756
98- current_app .logger .info (
99- "User login successful" ,
100- extra = {
101- "user_id" : authenticated_user .id ,
102- "username" : authenticated_user .username ,
103- "action" : "login" ,
104- "ip_address" : request .remote_addr ,
105- },
106- )
57+ # POST requests process login (for unit tests and backwards compatibility)
58+ username = request .form .get ("username" , "" ).strip ()
59+ password = request .form .get ("password" , "" )
10760
108- flash (f"Welcome, { authenticated_user .username } !" , "success" )
61+ # Basic validation
62+ if not username :
63+ flash ("Username is required" , "error" )
64+ return render_template ("auth/login.html" )
10965
110- # Redirect to next page or dashboard
111- # Validate next_page to prevent open redirect attacks
112- next_page = request .args .get ("next" )
113- if next_page :
114- # Only allow relative URLs (starting with /) to prevent open redirects
115- # Also reject protocol-relative URLs (starting with //)
116- if next_page .startswith ("/" ) and not next_page .startswith ("//" ):
117- return redirect (next_page )
118- return redirect (url_for ("dashboard.index" ))
66+ if not password :
67+ flash ("Password is required" , "error" )
68+ return render_template ("auth/login.html" )
11969
120- return render_template ("auth/login.html" )
70+ # Real authentication - validate credentials
71+ try :
72+ user = _authenticate_user (username , password )
73+ except RuntimeError as e :
74+ # Database initialization error
75+ error_message = str (e )
76+ flash (error_message , "error" )
77+ return render_template ("auth/login.html" )
78+
79+ if user is None :
80+ # Authentication failed - invalid credentials
81+ flash ("Invalid username or password" , "error" )
82+ return render_template ("auth/login.html" )
83+
84+ if user == "inactive" :
85+ # User account is inactive
86+ flash ("Account is inactive. Please contact an administrator." , "error" )
87+ return render_template ("auth/login.html" )
88+
89+ # At this point, user must be a User object (not None or "inactive")
90+ # Type narrowing: cast to User for mypy
91+ authenticated_user = cast (User , user )
92+
93+ # Authentication successful
94+ authenticated_user .last_login_at = datetime .now (timezone .utc )
95+ db .session .commit ()
96+
97+ # Set up session
98+ session ["user_id" ] = authenticated_user .id
99+ session ["username" ] = authenticated_user .username
100+ session ["is_admin" ] = authenticated_user .is_admin
101+
102+ current_app .logger .info (
103+ "User login successful" ,
104+ extra = {
105+ "user_id" : authenticated_user .id ,
106+ "username" : authenticated_user .username ,
107+ "action" : "login" ,
108+ "ip_address" : request .remote_addr ,
109+ },
110+ )
111+
112+ flash (f"Welcome, { authenticated_user .username } !" , "success" )
113+
114+ # Redirect to next page or dashboard
115+ # Validate next_page to prevent open redirect attacks
116+ next_page = request .args .get ("next" )
117+ if next_page :
118+ # Only allow relative URLs (starting with /) to prevent open redirects
119+ # Also reject protocol-relative URLs (starting with //)
120+ if next_page .startswith ("/" ) and not next_page .startswith ("//" ):
121+ return redirect (next_page )
122+ return redirect (url_for ("dashboard.index" ))
121123
122124
123125@bp .route ("/logout" )
@@ -146,7 +148,7 @@ def logout() -> Any:
146148 )
147149
148150 flash ("You have been logged out successfully." , "info" )
149- return redirect (url_for ("auth.login " ))
151+ return redirect (url_for ("serve_admin " ))
150152
151153
152154@bp .route ("/status" )
0 commit comments