disLodge commited on
Commit
bac337d
·
1 Parent(s): a6b79e3

Code verification error

Browse files
Files changed (2) hide show
  1. google_auth_flow.py +33 -18
  2. oauth_callback.py +1 -1
google_auth_flow.py CHANGED
@@ -1,24 +1,34 @@
1
  import os
2
- import json
 
3
  from google_auth_oauthlib.flow import Flow
4
  from google.oauth2.credentials import Credentials
5
  from google.auth.transport.requests import Request
6
  from dotenv import load_dotenv
7
 
 
 
 
8
  SCOPES = [
9
  "https://www.googleapis.com/auth/drive.readonly",
10
  "https://www.googleapis.com/auth/calendar.events",
11
  "https://www.googleapis.com/auth/userinfo.email",
 
12
  "openid",
13
  ]
 
14
  oauth_pkce_store: dict[str, str] = {}
15
  load_dotenv()
16
-
17
  CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "")
18
  CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "")
19
  REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI", "")
20
 
21
 
 
 
 
 
22
  def _client_config() -> dict:
23
  """Builds the client config dict that google_auth_oauthlib expects."""
24
  return {
@@ -31,40 +41,50 @@ def _client_config() -> dict:
31
  }
32
  }
33
 
 
34
  def get_auth_url(state: str | None = None) -> str:
35
  """
36
  Returns the Google OAuth consent-screen URL to redirect the user to.
37
  `state` can carry any context you want back in the callback (e.g. user_email).
38
  """
 
39
  flow = Flow.from_client_config(_client_config(), scopes=SCOPES)
40
  flow.redirect_uri = REDIRECT_URI
41
  auth_url, returned_state = flow.authorization_url(
42
- access_type="offline", # get refresh_token
43
  include_granted_scopes="true",
44
- prompt="consent", # force refresh_token every time during dev
45
- state=state or "",
46
  )
47
- oauth_pkce_store[returned_state] = flow.code_verifier
48
- print(">>> Stored PKCE verifier for state:", returned_state)
 
49
  return auth_url
50
 
 
51
  def exchange_code_for_token(code: str, state: str) -> dict:
52
  """
53
  Exchanges an authorization code (from the OAuth callback) for credentials.
54
  Returns a JSON-serialisable token dict.
55
  """
56
- flow = Flow.from_client_config(_client_config(), scopes=SCOPES)
57
- flow.redirect_uri = REDIRECT_URI
58
- code_verifier = oauth_pkce_store.get(state)
59
 
60
- print(">>> Retrieved verifier:", code_verifier)
 
 
 
 
61
 
 
 
62
  flow.code_verifier = code_verifier
63
-
64
  flow.fetch_token(code=code)
65
  creds = flow.credentials
66
  return _creds_to_dict(creds)
67
 
 
68
  def credentials_from_token_dict(token_dict: dict) -> Credentials:
69
  """
70
  Re-hydrates a Credentials object from a stored token dict,
@@ -82,6 +102,7 @@ def credentials_from_token_dict(token_dict: dict) -> Credentials:
82
  creds.refresh(Request())
83
  return creds
84
 
 
85
  def _creds_to_dict(creds: Credentials) -> dict:
86
  return {
87
  "token": creds.token,
@@ -92,9 +113,3 @@ def _creds_to_dict(creds: Credentials) -> dict:
92
  "scopes": list(creds.scopes or SCOPES),
93
  "expiry": creds.expiry.isoformat() if creds.expiry else None,
94
  }
95
-
96
-
97
-
98
-
99
-
100
-
 
1
  import os
2
+ from urllib.parse import unquote
3
+
4
  from google_auth_oauthlib.flow import Flow
5
  from google.oauth2.credentials import Credentials
6
  from google.auth.transport.requests import Request
7
  from dotenv import load_dotenv
8
 
9
+ # Google may grant extra scopes (e.g. userinfo.profile with openid); relax strict checks.
10
+ os.environ.setdefault("OAUTHLIB_RELAX_TOKEN_SCOPE", "1")
11
+
12
  SCOPES = [
13
  "https://www.googleapis.com/auth/drive.readonly",
14
  "https://www.googleapis.com/auth/calendar.events",
15
  "https://www.googleapis.com/auth/userinfo.email",
16
+ "https://www.googleapis.com/auth/userinfo.profile",
17
  "openid",
18
  ]
19
+
20
  oauth_pkce_store: dict[str, str] = {}
21
  load_dotenv()
22
+
23
  CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID", "")
24
  CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET", "")
25
  REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI", "")
26
 
27
 
28
+ def _normalize_state(state: str) -> str:
29
+ return unquote(state or "").strip()
30
+
31
+
32
  def _client_config() -> dict:
33
  """Builds the client config dict that google_auth_oauthlib expects."""
34
  return {
 
41
  }
42
  }
43
 
44
+
45
  def get_auth_url(state: str | None = None) -> str:
46
  """
47
  Returns the Google OAuth consent-screen URL to redirect the user to.
48
  `state` can carry any context you want back in the callback (e.g. user_email).
49
  """
50
+ user_state = _normalize_state(state or "")
51
  flow = Flow.from_client_config(_client_config(), scopes=SCOPES)
52
  flow.redirect_uri = REDIRECT_URI
53
  auth_url, returned_state = flow.authorization_url(
54
+ access_type="offline",
55
  include_granted_scopes="true",
56
+ prompt="consent",
57
+ state=user_state,
58
  )
59
+ store_key = _normalize_state(returned_state or user_state)
60
+ oauth_pkce_store[store_key] = flow.code_verifier
61
+ print(">>> Stored PKCE verifier for state:", store_key)
62
  return auth_url
63
 
64
+
65
  def exchange_code_for_token(code: str, state: str) -> dict:
66
  """
67
  Exchanges an authorization code (from the OAuth callback) for credentials.
68
  Returns a JSON-serialisable token dict.
69
  """
70
+ state_key = _normalize_state(state)
71
+ code_verifier = oauth_pkce_store.pop(state_key, None)
72
+ print(">>> Retrieved PKCE verifier for state:", state_key, "found:", bool(code_verifier))
73
 
74
+ if not code_verifier:
75
+ raise ValueError(
76
+ "No PKCE code verifier found for this sign-in. "
77
+ "Request a new auth link and complete it on the same server instance."
78
+ )
79
 
80
+ flow = Flow.from_client_config(_client_config(), scopes=SCOPES)
81
+ flow.redirect_uri = REDIRECT_URI
82
  flow.code_verifier = code_verifier
 
83
  flow.fetch_token(code=code)
84
  creds = flow.credentials
85
  return _creds_to_dict(creds)
86
 
87
+
88
  def credentials_from_token_dict(token_dict: dict) -> Credentials:
89
  """
90
  Re-hydrates a Credentials object from a stored token dict,
 
102
  creds.refresh(Request())
103
  return creds
104
 
105
+
106
  def _creds_to_dict(creds: Credentials) -> dict:
107
  return {
108
  "token": creds.token,
 
113
  "scopes": list(creds.scopes or SCOPES),
114
  "expiry": creds.expiry.isoformat() if creds.expiry else None,
115
  }
 
 
 
 
 
 
oauth_callback.py CHANGED
@@ -32,7 +32,7 @@ def handle_oauth_callback(code: str, state: str) -> dict:
32
  return {
33
  "success": True,
34
  "user_email": user_email,
35
- "message": f"✅ Google Drive access granted and token saved for {user_email}. You can now search your Drive.",
36
  }
37
  except Exception as e:
38
  return {
 
32
  return {
33
  "success": True,
34
  "user_email": user_email,
35
+ "message": f"✅ Google access granted and token saved for {user_email}. You can retry your request.",
36
  }
37
  except Exception as e:
38
  return {