@@ -50,15 +50,34 @@ def __init__(
50
50
# finally call validate
51
51
self .validate ()
52
52
53
- def login (self , password : str ) -> LoginDTO :
54
- """Logs in on the Actual server using the password provided. Raises `AuthorizationError` if it fails to
55
- authenticate the user."""
53
+ def login (self , password : str , method : Literal ["password" , "header" ] = "password" ) -> LoginDTO :
54
+ """
55
+ Logs in on the Actual server using the password provided. Raises `AuthorizationError` if it fails to
56
+ authenticate the user.
57
+
58
+ :param password: password of the Actual server.
59
+ :param method: the method used to authenticate with the server. Check
60
+ https://actualbudget.org/docs/advanced/http-header-auth/ for information.
61
+ """
56
62
if not password :
57
63
raise AuthorizationError ("Trying to login but not password was provided." )
58
- response = requests .post (f"{ self .api_url } /{ Endpoints .LOGIN } " , json = {"password" : password })
64
+ if method == "password" :
65
+ response = requests .post (f"{ self .api_url } /{ Endpoints .LOGIN } " , json = {"password" : password })
66
+ else :
67
+ response = requests .post (
68
+ f"{ self .api_url } /{ Endpoints .LOGIN } " ,
69
+ json = {"loginMethod" : method },
70
+ headers = {"X-ACTUAL-PASSWORD" : password },
71
+ )
72
+ response_dict = response .json ()
59
73
if response .status_code == 400 and "invalid-password" in response .text :
60
74
raise AuthorizationError ("Could not validate password on login." )
61
- response .raise_for_status ()
75
+ elif response .status_code == 200 and "invalid-header" in response .text :
76
+ # try the same login with the header
77
+ return self .login (password , "header" )
78
+ elif response_dict ["status" ] == "error" :
79
+ # for example, when not trusting the proxy
80
+ raise AuthorizationError (f"Something went wrong on login: { response_dict ['reason' ]} " )
62
81
login_response = LoginDTO .model_validate (response .json ())
63
82
# older versions do not return 400 but rather return empty tokens
64
83
if login_response .data .token is None :
@@ -84,7 +103,7 @@ def info(self) -> InfoDTO:
84
103
return InfoDTO .model_validate (response .json ())
85
104
86
105
def validate (self ) -> ValidateDTO :
87
- """Validates"""
106
+ """Validates if the user is valid and logged in, and if the token is also valid and bound to a session. """
88
107
response = requests .get (f"{ self .api_url } /{ Endpoints .ACCOUNT_VALIDATE } " , headers = self .headers ())
89
108
response .raise_for_status ()
90
109
return ValidateDTO .model_validate (response .json ())
0 commit comments