11import logging
22import os
3- from threading import Timer
43from typing import Dict , List
54
65import dotenv
7- from fastapi import FastAPI
6+ from fastapi . applications import FastAPI
87from fastapi .params import Depends
98from fastapi .requests import Request
109from fastapi .responses import Response
@@ -29,7 +28,8 @@ class Authenticator:
2928 def __init__ (
3029 self ,
3130 app : FastAPI ,
32- params : models .Params | List [models .Params ],
31+ params : models .Parameters | List [models .Parameters ],
32+ timeout : int = 300 ,
3333 username : str = os .environ .get ("USERNAME" ),
3434 password : str = os .environ .get ("PASSWORD" ),
3535 fallback_button : str = models .fallback .button ,
@@ -39,6 +39,8 @@ def __init__(
3939
4040 Args:
4141 app: FastAPI application instance to which the authenticator will be added.
42+ params: Parameters for the secure routes, can be a single `Parameters` object or a list of `Parameters`.
43+ timeout: Session timeout in seconds, default is 300 seconds (5 minutes).
4244 username: Username for authentication, can be set via environment variable 'USERNAME'.
4345 password: Password for authentication, can be set via environment variable 'PASSWORD'.
4446 fallback_button: Title for the fallback button, defaults to "LOGIN".
@@ -51,10 +53,10 @@ def __init__(
5153
5254 if isinstance (params , list ):
5355 self .params = params
54- elif isinstance (params , models .Params ):
56+ elif isinstance (params , models .Parameters ):
5557 self .params = [params ]
5658
57- self .route_map : Dict [str , models .Params ] = {
59+ self .route_map : Dict [str , models .Parameters ] = {
5860 param .path : param for param in self .params if param .route is APIRoute
5961 }
6062
@@ -69,6 +71,7 @@ def __init__(
6971
7072 self .username = username
7173 self .password = password
74+ self .timeout = timeout
7275
7376 self ._secure ()
7477
@@ -95,84 +98,28 @@ def _verify_auth(
9598 env_username = self .username ,
9699 env_password = self .password ,
97100 )
98- referer = request .headers .get ("Referer" )
99- origin = request .headers .get ("Origin" )
100- destination = referer .replace (origin , "" )
101+ destination = request .cookies .get ("X-Requested-By" )
101102 parameter = self .route_map .get (destination )
102- private_route = APIRoute (
103- path = parameter .path ,
104- endpoint = parameter .function ,
105- methods = parameter .methods ,
106- dependencies = [Depends (utils .session_check )],
107- )
108- for route in self .app .routes :
109- if route .path == private_route .path :
110- LOGGER .info (
111- "Route %s already exists, removing it to replace with secure route." ,
112- private_route .path ,
113- )
114- self .app .routes .remove (route )
115- break
116- self .app .routes .append (private_route )
117- LOGGER .info ("Setting session timeout for %s seconds" , parameter .timeout )
118- self ._handle_session (
119- response = response ,
120- request = request ,
121- secure_route = private_route ,
122- timeout = parameter .timeout ,
123- )
124- return {"redirect_url" : parameter .path }
125-
126- def _setup_session_route (self , secure_route : APIRoute ) -> None :
127- """Removes the secure route and adds a routing logic for invalid sessions.
128-
129- Args:
130- secure_route: Secure route to be removed from the app after the session timeout.
131- """
132- LOGGER .info ("Session expired, removing secure route: %s" , secure_route .path )
133- self .app .routes .remove (secure_route )
134- LOGGER .info (
135- "Adding session route to handle expired sessions at %s" , secure_route .path
136- )
137- self .app .routes .append (
138- APIRoute (
139- path = secure_route .path ,
140- endpoint = endpoints .session ,
141- methods = ["GET" ],
142- )
143- )
144-
145- def _handle_session (
146- self ,
147- response : Response ,
148- request : Request ,
149- secure_route : APIRoute ,
150- timeout : int ,
151- ) -> None :
152- """Handle session management by setting a cookie and scheduling session removal.
153-
154- Args:
155- response: Response object to set the session cookie.
156- request: Request object containing client information.
157- secure_route: Secure route to be removed from the app after the session timeout.
158- """
159- # Remove the secure route after the session timeout - backend
160- Timer (
161- function = self ._setup_session_route ,
162- args = (secure_route ,),
163- interval = timeout ,
164- ).start ()
165- # Set the max age in session cookie to session timeout - frontend
103+ LOGGER .info ("Setting session timeout for %s seconds" , self .timeout )
104+ # Set session_token cookie with a timeout, to be used for session validation when redirected
166105 response .set_cookie (
167106 key = "session_token" ,
168107 value = models .ws_session .client_auth [request .client .host ].get ("token" ),
169108 httponly = True ,
170109 samesite = "strict" ,
171- max_age = timeout ,
110+ max_age = self . timeout ,
172111 )
112+ # todo: Session should be cleared at client side after timeout
113+ response .delete_cookie (key = "X-Requested-By" )
114+ return {"redirect_url" : parameter .path }
173115
174116 def _secure (self ) -> None :
175117 """Create the login and verification routes for the APIAuthenticator."""
118+ login_route = APIRoute (
119+ path = enums .APIEndpoints .fastapi_login ,
120+ endpoint = endpoints .login ,
121+ methods = ["GET" ],
122+ )
176123 error_route = APIRoute (
177124 path = enums .APIEndpoints .fastapi_error ,
178125 endpoint = endpoints .error ,
@@ -190,6 +137,7 @@ def _secure(self) -> None:
190137 )
191138 for param in self .params :
192139 if param .route is APIWebSocketRoute :
140+ # WebSocket routes will not have a login path, they will be protected by session check
193141 secure_route = APIWebSocketRoute (
194142 path = param .path ,
195143 endpoint = param .function ,
@@ -198,8 +146,9 @@ def _secure(self) -> None:
198146 else :
199147 secure_route = APIRoute (
200148 path = param .path ,
201- endpoint = endpoints . login ,
149+ endpoint = param . function ,
202150 methods = ["GET" ],
151+ dependencies = [Depends (utils .session_check )],
203152 )
204153 self .app .routes .append (secure_route )
205- self .app .routes .extend ([session_route , verify_route , error_route ])
154+ self .app .routes .extend ([login_route , session_route , verify_route , error_route ])
0 commit comments