-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.py
More file actions
120 lines (94 loc) · 4.02 KB
/
Copy pathauth.py
File metadata and controls
120 lines (94 loc) · 4.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Session identity and OAuth wiring for per-user document isolation.
Owner identity resolution follows Design 4's principle that the authorization
boundary must exist *before* retrieval, never in prompts:
- Google OAuth (when OAUTH_GOOGLE_* env vars are configured) maps a verified
email to the stable owner id ``google:<email>``.
- Without OAuth, sessions degrade to ``anon:<chainlit-session-id>`` so local
development stays frictionless while uploads remain isolated per browser
session.
- ``AUTH_REQUIRED=true`` hard-refuses unauthenticated sessions instead of
degrading to anonymous owners.
Chainlit is imported lazily so this module stays cheap to unit-test and can
never trigger Chainlit's import-time OAuth provider validation outside app.py.
"""
import os
from typing import Any, Dict, Optional
from observability import get_logger
logger = get_logger("auth")
def google_oauth_configured() -> bool:
"""True when both Google OAuth env vars are present."""
return bool(os.getenv("OAUTH_GOOGLE_CLIENT_ID")) and bool(
os.getenv("OAUTH_GOOGLE_CLIENT_SECRET")
)
def auth_required() -> bool:
"""When true, sessions without an authenticated identity are refused."""
return os.getenv("AUTH_REQUIRED", "false").strip().lower() in ("1", "true", "yes", "on")
def build_oauth_user(provider_id: str, raw_user_data: Dict[str, Any]):
"""Map provider profile data to a Chainlit user with a stable identifier.
Returns ``None`` to reject the login when the provider is unsupported or
no verified email is available - isolation depends on stable ids.
"""
if provider_id != "google":
logger.warning(f"Unsupported OAuth provider requested: {provider_id}")
return None
from chainlit import User
email = (raw_user_data.get("email") or "").strip().lower()
if not email:
logger.warning("Google OAuth payload missing email; rejecting login")
return None
return User(
identifier=f"google:{email}",
display_name=raw_user_data.get("name") or email,
metadata={"provider": "google", "email": email},
)
def register_oauth() -> bool:
"""Register Chainlit's oauth_callback when Google OAuth is configured.
The ``@cl.oauth_callback`` decorator raises at decoration time when no
provider env vars exist, so registration is conditional. Returns True
when the callback was registered.
"""
if not google_oauth_configured():
logger.info(
"OAuth not configured (missing OAUTH_GOOGLE_*); "
"sessions will be isolated anonymously"
)
return False
import chainlit as cl
@cl.oauth_callback
async def _oauth_callback(
provider_id: str,
token: str,
raw_user_data: Dict[str, Any],
default_user,
id_token: Optional[str] = None,
):
user = build_oauth_user(provider_id, raw_user_data)
if user is not None:
# Registry write is best-effort; login never depends on storage.
try:
from store import get_store
get_store().upsert_user(
user.identifier,
email=user.metadata.get("email"),
name=user.display_name,
provider=provider_id,
)
except Exception as e:
logger.warning(f"Could not persist user record: {e}")
return user
logger.info("Google OAuth login enabled")
return True
def resolve_owner_id(user: Any = None, session_id: Optional[str] = None) -> Optional[str]:
"""Resolve the requester's owner id for retrieval isolation.
Precedence: authenticated user identifier, then anonymous session id.
Returns None only when authentication is required but absent (the caller
must refuse the request) or when there is nothing to key on.
"""
identifier = getattr(user, "identifier", None)
if identifier:
return str(identifier)
if auth_required():
return None
if session_id:
return f"anon:{session_id}"
return None