remdb 0.3.242__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (235) hide show
  1. rem/__init__.py +129 -0
  2. rem/agentic/README.md +760 -0
  3. rem/agentic/__init__.py +54 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +38 -0
  6. rem/agentic/agents/agent_manager.py +311 -0
  7. rem/agentic/agents/sse_simulator.py +502 -0
  8. rem/agentic/context.py +425 -0
  9. rem/agentic/context_builder.py +360 -0
  10. rem/agentic/llm_provider_models.py +301 -0
  11. rem/agentic/mcp/__init__.py +0 -0
  12. rem/agentic/mcp/tool_wrapper.py +273 -0
  13. rem/agentic/otel/__init__.py +5 -0
  14. rem/agentic/otel/setup.py +240 -0
  15. rem/agentic/providers/phoenix.py +926 -0
  16. rem/agentic/providers/pydantic_ai.py +854 -0
  17. rem/agentic/query.py +117 -0
  18. rem/agentic/query_helper.py +89 -0
  19. rem/agentic/schema.py +737 -0
  20. rem/agentic/serialization.py +245 -0
  21. rem/agentic/tools/__init__.py +5 -0
  22. rem/agentic/tools/rem_tools.py +242 -0
  23. rem/api/README.md +657 -0
  24. rem/api/deps.py +253 -0
  25. rem/api/main.py +460 -0
  26. rem/api/mcp_router/prompts.py +182 -0
  27. rem/api/mcp_router/resources.py +820 -0
  28. rem/api/mcp_router/server.py +243 -0
  29. rem/api/mcp_router/tools.py +1605 -0
  30. rem/api/middleware/tracking.py +172 -0
  31. rem/api/routers/admin.py +520 -0
  32. rem/api/routers/auth.py +898 -0
  33. rem/api/routers/chat/__init__.py +5 -0
  34. rem/api/routers/chat/child_streaming.py +394 -0
  35. rem/api/routers/chat/completions.py +702 -0
  36. rem/api/routers/chat/json_utils.py +76 -0
  37. rem/api/routers/chat/models.py +202 -0
  38. rem/api/routers/chat/otel_utils.py +33 -0
  39. rem/api/routers/chat/sse_events.py +546 -0
  40. rem/api/routers/chat/streaming.py +950 -0
  41. rem/api/routers/chat/streaming_utils.py +327 -0
  42. rem/api/routers/common.py +18 -0
  43. rem/api/routers/dev.py +87 -0
  44. rem/api/routers/feedback.py +276 -0
  45. rem/api/routers/messages.py +620 -0
  46. rem/api/routers/models.py +86 -0
  47. rem/api/routers/query.py +362 -0
  48. rem/api/routers/shared_sessions.py +422 -0
  49. rem/auth/README.md +258 -0
  50. rem/auth/__init__.py +36 -0
  51. rem/auth/jwt.py +367 -0
  52. rem/auth/middleware.py +318 -0
  53. rem/auth/providers/__init__.py +16 -0
  54. rem/auth/providers/base.py +376 -0
  55. rem/auth/providers/email.py +215 -0
  56. rem/auth/providers/google.py +163 -0
  57. rem/auth/providers/microsoft.py +237 -0
  58. rem/cli/README.md +517 -0
  59. rem/cli/__init__.py +8 -0
  60. rem/cli/commands/README.md +299 -0
  61. rem/cli/commands/__init__.py +3 -0
  62. rem/cli/commands/ask.py +549 -0
  63. rem/cli/commands/cluster.py +1808 -0
  64. rem/cli/commands/configure.py +495 -0
  65. rem/cli/commands/db.py +828 -0
  66. rem/cli/commands/dreaming.py +324 -0
  67. rem/cli/commands/experiments.py +1698 -0
  68. rem/cli/commands/mcp.py +66 -0
  69. rem/cli/commands/process.py +388 -0
  70. rem/cli/commands/query.py +109 -0
  71. rem/cli/commands/scaffold.py +47 -0
  72. rem/cli/commands/schema.py +230 -0
  73. rem/cli/commands/serve.py +106 -0
  74. rem/cli/commands/session.py +453 -0
  75. rem/cli/dreaming.py +363 -0
  76. rem/cli/main.py +123 -0
  77. rem/config.py +244 -0
  78. rem/mcp_server.py +41 -0
  79. rem/models/core/__init__.py +49 -0
  80. rem/models/core/core_model.py +70 -0
  81. rem/models/core/engram.py +333 -0
  82. rem/models/core/experiment.py +672 -0
  83. rem/models/core/inline_edge.py +132 -0
  84. rem/models/core/rem_query.py +246 -0
  85. rem/models/entities/__init__.py +68 -0
  86. rem/models/entities/domain_resource.py +38 -0
  87. rem/models/entities/feedback.py +123 -0
  88. rem/models/entities/file.py +57 -0
  89. rem/models/entities/image_resource.py +88 -0
  90. rem/models/entities/message.py +64 -0
  91. rem/models/entities/moment.py +123 -0
  92. rem/models/entities/ontology.py +181 -0
  93. rem/models/entities/ontology_config.py +131 -0
  94. rem/models/entities/resource.py +95 -0
  95. rem/models/entities/schema.py +87 -0
  96. rem/models/entities/session.py +84 -0
  97. rem/models/entities/shared_session.py +180 -0
  98. rem/models/entities/subscriber.py +175 -0
  99. rem/models/entities/user.py +93 -0
  100. rem/py.typed +0 -0
  101. rem/registry.py +373 -0
  102. rem/schemas/README.md +507 -0
  103. rem/schemas/__init__.py +6 -0
  104. rem/schemas/agents/README.md +92 -0
  105. rem/schemas/agents/core/agent-builder.yaml +235 -0
  106. rem/schemas/agents/core/moment-builder.yaml +178 -0
  107. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  108. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  109. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  110. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  111. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  112. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  113. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  114. rem/schemas/agents/examples/hello-world.yaml +37 -0
  115. rem/schemas/agents/examples/query.yaml +54 -0
  116. rem/schemas/agents/examples/simple.yaml +21 -0
  117. rem/schemas/agents/examples/test.yaml +29 -0
  118. rem/schemas/agents/rem.yaml +132 -0
  119. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  120. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  121. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  122. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  123. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  124. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  125. rem/services/__init__.py +18 -0
  126. rem/services/audio/INTEGRATION.md +308 -0
  127. rem/services/audio/README.md +376 -0
  128. rem/services/audio/__init__.py +15 -0
  129. rem/services/audio/chunker.py +354 -0
  130. rem/services/audio/transcriber.py +259 -0
  131. rem/services/content/README.md +1269 -0
  132. rem/services/content/__init__.py +5 -0
  133. rem/services/content/providers.py +760 -0
  134. rem/services/content/service.py +762 -0
  135. rem/services/dreaming/README.md +230 -0
  136. rem/services/dreaming/__init__.py +53 -0
  137. rem/services/dreaming/affinity_service.py +322 -0
  138. rem/services/dreaming/moment_service.py +251 -0
  139. rem/services/dreaming/ontology_service.py +54 -0
  140. rem/services/dreaming/user_model_service.py +297 -0
  141. rem/services/dreaming/utils.py +39 -0
  142. rem/services/email/__init__.py +10 -0
  143. rem/services/email/service.py +522 -0
  144. rem/services/email/templates.py +360 -0
  145. rem/services/embeddings/__init__.py +11 -0
  146. rem/services/embeddings/api.py +127 -0
  147. rem/services/embeddings/worker.py +435 -0
  148. rem/services/fs/README.md +662 -0
  149. rem/services/fs/__init__.py +62 -0
  150. rem/services/fs/examples.py +206 -0
  151. rem/services/fs/examples_paths.py +204 -0
  152. rem/services/fs/git_provider.py +935 -0
  153. rem/services/fs/local_provider.py +760 -0
  154. rem/services/fs/parsing-hooks-examples.md +172 -0
  155. rem/services/fs/paths.py +276 -0
  156. rem/services/fs/provider.py +460 -0
  157. rem/services/fs/s3_provider.py +1042 -0
  158. rem/services/fs/service.py +186 -0
  159. rem/services/git/README.md +1075 -0
  160. rem/services/git/__init__.py +17 -0
  161. rem/services/git/service.py +469 -0
  162. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  163. rem/services/phoenix/README.md +453 -0
  164. rem/services/phoenix/__init__.py +46 -0
  165. rem/services/phoenix/client.py +960 -0
  166. rem/services/phoenix/config.py +88 -0
  167. rem/services/phoenix/prompt_labels.py +477 -0
  168. rem/services/postgres/README.md +757 -0
  169. rem/services/postgres/__init__.py +49 -0
  170. rem/services/postgres/diff_service.py +599 -0
  171. rem/services/postgres/migration_service.py +427 -0
  172. rem/services/postgres/programmable_diff_service.py +635 -0
  173. rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
  174. rem/services/postgres/register_type.py +353 -0
  175. rem/services/postgres/repository.py +481 -0
  176. rem/services/postgres/schema_generator.py +661 -0
  177. rem/services/postgres/service.py +802 -0
  178. rem/services/postgres/sql_builder.py +355 -0
  179. rem/services/rate_limit.py +113 -0
  180. rem/services/rem/README.md +318 -0
  181. rem/services/rem/__init__.py +23 -0
  182. rem/services/rem/exceptions.py +71 -0
  183. rem/services/rem/executor.py +293 -0
  184. rem/services/rem/parser.py +180 -0
  185. rem/services/rem/queries.py +196 -0
  186. rem/services/rem/query.py +371 -0
  187. rem/services/rem/service.py +608 -0
  188. rem/services/session/README.md +374 -0
  189. rem/services/session/__init__.py +13 -0
  190. rem/services/session/compression.py +488 -0
  191. rem/services/session/pydantic_messages.py +310 -0
  192. rem/services/session/reload.py +85 -0
  193. rem/services/user_service.py +130 -0
  194. rem/settings.py +1877 -0
  195. rem/sql/background_indexes.sql +52 -0
  196. rem/sql/migrations/001_install.sql +983 -0
  197. rem/sql/migrations/002_install_models.sql +3157 -0
  198. rem/sql/migrations/003_optional_extensions.sql +326 -0
  199. rem/sql/migrations/004_cache_system.sql +282 -0
  200. rem/sql/migrations/005_schema_update.sql +145 -0
  201. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  202. rem/utils/AGENTIC_CHUNKING.md +597 -0
  203. rem/utils/README.md +628 -0
  204. rem/utils/__init__.py +61 -0
  205. rem/utils/agentic_chunking.py +622 -0
  206. rem/utils/batch_ops.py +343 -0
  207. rem/utils/chunking.py +108 -0
  208. rem/utils/clip_embeddings.py +276 -0
  209. rem/utils/constants.py +97 -0
  210. rem/utils/date_utils.py +228 -0
  211. rem/utils/dict_utils.py +98 -0
  212. rem/utils/embeddings.py +436 -0
  213. rem/utils/examples/embeddings_example.py +305 -0
  214. rem/utils/examples/sql_types_example.py +202 -0
  215. rem/utils/files.py +323 -0
  216. rem/utils/markdown.py +16 -0
  217. rem/utils/mime_types.py +158 -0
  218. rem/utils/model_helpers.py +492 -0
  219. rem/utils/schema_loader.py +649 -0
  220. rem/utils/sql_paths.py +146 -0
  221. rem/utils/sql_types.py +350 -0
  222. rem/utils/user_id.py +81 -0
  223. rem/utils/vision.py +325 -0
  224. rem/workers/README.md +506 -0
  225. rem/workers/__init__.py +7 -0
  226. rem/workers/db_listener.py +579 -0
  227. rem/workers/db_maintainer.py +74 -0
  228. rem/workers/dreaming.py +502 -0
  229. rem/workers/engram_processor.py +312 -0
  230. rem/workers/sqs_file_processor.py +193 -0
  231. rem/workers/unlogged_maintainer.py +463 -0
  232. remdb-0.3.242.dist-info/METADATA +1632 -0
  233. remdb-0.3.242.dist-info/RECORD +235 -0
  234. remdb-0.3.242.dist-info/WHEEL +4 -0
  235. remdb-0.3.242.dist-info/entry_points.txt +2 -0
rem/auth/middleware.py ADDED
@@ -0,0 +1,318 @@
1
+ """
2
+ Authentication Middleware for FastAPI.
3
+
4
+ Protects API endpoints by requiring valid authentication.
5
+ Supports multiple auth methods: JWT, API Key, Session, Dev Token.
6
+ Anonymous access with rate limiting when allow_anonymous=True.
7
+ MCP endpoints are always protected unless explicitly disabled.
8
+
9
+ Design Pattern:
10
+ - API Key (X-API-Key): Access control guardrail, NOT user identity
11
+ - JWT (Authorization: Bearer): Primary method for user identity
12
+ - Dev token: Non-production testing (starts with "dev_")
13
+ - Session: Backward compatibility for browser-based auth
14
+ - MCP paths always require authentication (protected service)
15
+
16
+ Authentication Flow:
17
+ 1. Check JWT/dev token/session for user identity first
18
+ 2. If user is admin: bypass API key check (admin privilege)
19
+ 3. If API key enabled and user is not admin: Validate X-API-Key header
20
+ 4. If allow_anonymous=True: Allow as anonymous (rate-limited)
21
+ 5. If allow_anonymous=False: Return 401 / redirect to login
22
+
23
+ IMPORTANT: API key validates ACCESS, JWT identifies USER.
24
+ Admin users bypass the API key requirement (trusted identity).
25
+
26
+ Access Modes (configured in settings.auth):
27
+ - enabled=true, allow_anonymous=true: Auth available, anonymous gets rate-limited access
28
+ - enabled=true, allow_anonymous=false: Auth required for all requests
29
+ - enabled=false: Middleware not loaded, all requests pass through
30
+ - mcp_requires_auth=true (default): MCP always requires login regardless of allow_anonymous
31
+ - mcp_requires_auth=false: MCP follows normal allow_anonymous rules (dev only)
32
+
33
+ API Key Authentication (configured in settings.api):
34
+ - api_key_enabled=true: Require X-API-Key header for access
35
+ - api_key: The secret key to validate against
36
+ - API key is an ACCESS GATE, not user identity - JWT still needed for user
37
+
38
+ Dev Token Support (non-production only):
39
+ - GET /api/auth/dev/token returns a Bearer token for test-user
40
+ - Include as: Authorization: Bearer dev_<signature>
41
+ - Only works when ENVIRONMENT != "production"
42
+
43
+ Usage:
44
+ from rem.auth.middleware import AuthMiddleware
45
+
46
+ app.add_middleware(
47
+ AuthMiddleware,
48
+ protected_paths=["/api/v1"],
49
+ excluded_paths=["/api/auth", "/health"],
50
+ allow_anonymous=settings.auth.allow_anonymous,
51
+ mcp_requires_auth=settings.auth.mcp_requires_auth,
52
+ )
53
+ """
54
+
55
+ from starlette.middleware.base import BaseHTTPMiddleware
56
+ from starlette.requests import Request
57
+ from starlette.responses import JSONResponse, RedirectResponse
58
+ from loguru import logger
59
+
60
+ from ..settings import settings
61
+
62
+
63
+ class AuthMiddleware(BaseHTTPMiddleware):
64
+ """
65
+ Authentication middleware using session-based auth.
66
+
67
+ Checks for valid user session on protected paths.
68
+ Compatible with OAuth flows from auth router.
69
+ Supports anonymous access with rate limiting.
70
+ MCP endpoints are always protected unless explicitly disabled.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ app,
76
+ protected_paths: list[str] | None = None,
77
+ excluded_paths: list[str] | None = None,
78
+ allow_anonymous: bool = True,
79
+ mcp_requires_auth: bool = True,
80
+ mcp_path: str = "/api/v1/mcp",
81
+ ):
82
+ """
83
+ Initialize auth middleware.
84
+
85
+ Args:
86
+ app: ASGI application
87
+ protected_paths: Paths that require authentication
88
+ excluded_paths: Paths to exclude from auth check
89
+ allow_anonymous: Allow unauthenticated requests (rate-limited)
90
+ mcp_requires_auth: Always require auth for MCP (protected service)
91
+ mcp_path: Path prefix for MCP endpoints
92
+ """
93
+ super().__init__(app)
94
+ self.protected_paths = protected_paths or ["/api/v1"]
95
+ self.excluded_paths = excluded_paths or ["/api/auth", "/health", "/docs", "/openapi.json"]
96
+ self.allow_anonymous = allow_anonymous
97
+ self.mcp_requires_auth = mcp_requires_auth
98
+ self.mcp_path = mcp_path
99
+
100
+ def _check_api_key(self, request: Request) -> dict | None:
101
+ """
102
+ Check for valid X-API-Key header.
103
+
104
+ Returns:
105
+ API key user dict if valid, None otherwise
106
+ """
107
+ # Only check if API key auth is enabled
108
+ if not settings.api.api_key_enabled:
109
+ return None
110
+
111
+ # Check for X-API-Key header
112
+ api_key = request.headers.get("x-api-key")
113
+ if not api_key:
114
+ return None
115
+
116
+ # Validate against configured API key
117
+ if settings.api.api_key and api_key == settings.api.api_key:
118
+ logger.debug("X-API-Key authenticated")
119
+ return {
120
+ "id": "api-key-user",
121
+ "email": "api@rem.local",
122
+ "name": "API Key User",
123
+ "provider": "api-key",
124
+ "tenant_id": "default",
125
+ "tier": "pro", # API key users get full access
126
+ "roles": ["user"],
127
+ }
128
+
129
+ # Invalid API key
130
+ logger.warning("Invalid X-API-Key provided")
131
+ return None
132
+
133
+ def _check_jwt_token(self, request: Request) -> dict | None:
134
+ """
135
+ Check for valid JWT in Authorization header.
136
+
137
+ Returns:
138
+ User dict if valid JWT, None otherwise
139
+ """
140
+ auth_header = request.headers.get("authorization", "")
141
+ if not auth_header.startswith("Bearer "):
142
+ return None
143
+
144
+ token = auth_header[7:] # Strip "Bearer "
145
+
146
+ # Skip dev tokens (handled separately)
147
+ if token.startswith("dev_"):
148
+ return None
149
+
150
+ # Verify JWT token
151
+ from .jwt import get_jwt_service
152
+ jwt_service = get_jwt_service()
153
+ user = jwt_service.verify_token(token)
154
+
155
+ if user:
156
+ logger.debug(f"JWT authenticated: {user.get('email')}")
157
+ return user
158
+
159
+ return None
160
+
161
+ def _check_dev_token(self, request: Request) -> dict | None:
162
+ """
163
+ Check for valid dev token in Authorization header (non-production only).
164
+
165
+ Returns:
166
+ Test user dict if valid dev token, None otherwise
167
+ """
168
+ if settings.environment == "production":
169
+ return None
170
+
171
+ auth_header = request.headers.get("authorization", "")
172
+ if not auth_header.startswith("Bearer "):
173
+ return None
174
+
175
+ token = auth_header[7:] # Strip "Bearer "
176
+
177
+ # Only check dev tokens (start with "dev_")
178
+ if not token.startswith("dev_"):
179
+ return None
180
+
181
+ # Verify dev token
182
+ from ..api.routers.dev import verify_dev_token
183
+ if verify_dev_token(token):
184
+ logger.debug("Dev token authenticated as test-user")
185
+ return {
186
+ "id": "test-user",
187
+ "email": "test@rem.local",
188
+ "name": "Test User",
189
+ "provider": "dev",
190
+ "tenant_id": "default",
191
+ "tier": "pro", # Give test user pro tier for full access
192
+ "roles": ["admin"],
193
+ }
194
+
195
+ return None
196
+
197
+ def _is_admin(self, user: dict | None) -> bool:
198
+ """Check if user has admin role."""
199
+ if not user:
200
+ return False
201
+ return "admin" in user.get("roles", [])
202
+
203
+ async def dispatch(self, request: Request, call_next):
204
+ """
205
+ Check authentication for protected paths.
206
+
207
+ Args:
208
+ request: HTTP request
209
+ call_next: Next middleware in chain
210
+
211
+ Returns:
212
+ Response (401/redirect if unauthorized, normal response if authorized/anonymous)
213
+ """
214
+ path = request.url.path
215
+
216
+ # Check if path is protected
217
+ is_protected = any(path.startswith(p) for p in self.protected_paths)
218
+ is_excluded = any(path.startswith(p) for p in self.excluded_paths)
219
+
220
+ # Check if this is an MCP path (paid service, always requires auth)
221
+ is_mcp_path = path.startswith(self.mcp_path)
222
+
223
+ # Skip auth check for excluded paths
224
+ if not is_protected or is_excluded:
225
+ return await call_next(request)
226
+
227
+ # Check for user identity FIRST (JWT, dev token, session)
228
+ # This allows admin users to bypass API key requirement
229
+ user = None
230
+
231
+ # Check for JWT token in Authorization header (primary user identity)
232
+ jwt_user = self._check_jwt_token(request)
233
+ if jwt_user:
234
+ user = jwt_user
235
+
236
+ # Check for dev token (non-production only)
237
+ if not user:
238
+ dev_user = self._check_dev_token(request)
239
+ if dev_user:
240
+ user = dev_user
241
+
242
+ # Check for valid session (backward compatibility)
243
+ if not user:
244
+ session_user = request.session.get("user")
245
+ if session_user:
246
+ user = session_user
247
+
248
+ # If user is admin, bypass API key check entirely
249
+ if self._is_admin(user):
250
+ logger.debug(f"Admin user {user.get('email')} bypassing API key check")
251
+ request.state.user = user
252
+ request.state.is_anonymous = False
253
+ return await call_next(request)
254
+
255
+ # API key validation for non-admin users (access control guardrail)
256
+ if settings.api.api_key_enabled:
257
+ api_key = request.headers.get("x-api-key")
258
+ if not api_key:
259
+ logger.debug(f"Missing X-API-Key for: {path}")
260
+ return JSONResponse(
261
+ status_code=401,
262
+ content={"detail": "API key required. Include X-API-Key header."},
263
+ headers={"WWW-Authenticate": 'ApiKey realm="REM API"'},
264
+ )
265
+ if api_key != settings.api.api_key:
266
+ logger.warning(f"Invalid X-API-Key for: {path}")
267
+ return JSONResponse(
268
+ status_code=401,
269
+ content={"detail": "Invalid API key"},
270
+ headers={"WWW-Authenticate": 'ApiKey realm="REM API"'},
271
+ )
272
+ logger.debug("X-API-Key validated for access")
273
+
274
+ # If we have a valid user (non-admin, but passed API key check), allow access
275
+ if user:
276
+ request.state.user = user
277
+ request.state.is_anonymous = False
278
+ return await call_next(request)
279
+
280
+ # No user session - check if MCP path requires auth
281
+ if is_mcp_path and self.mcp_requires_auth:
282
+ # MCP is a protected service - always require authentication
283
+ logger.warning(f"Unauthorized MCP access attempt: {path}")
284
+ return JSONResponse(
285
+ status_code=401,
286
+ content={
287
+ "detail": "Authentication required for MCP. Please login to use this service.",
288
+ "code": "MCP_AUTH_REQUIRED",
289
+ },
290
+ headers={
291
+ "WWW-Authenticate": 'Bearer realm="REM MCP"',
292
+ },
293
+ )
294
+
295
+ # No user session - handle anonymous access for non-MCP paths
296
+ if self.allow_anonymous:
297
+ # Allow anonymous access - rate limiting handled downstream
298
+ request.state.user = None
299
+ request.state.is_anonymous = True
300
+ logger.debug(f"Anonymous access: {path}")
301
+ return await call_next(request)
302
+
303
+ # Anonymous not allowed - require authentication
304
+ logger.warning(f"Unauthorized access attempt: {path}")
305
+
306
+ # Return 401 for API requests (JSON)
307
+ accept = request.headers.get("accept", "")
308
+ if "application/json" in accept or path.startswith("/api/"):
309
+ return JSONResponse(
310
+ status_code=401,
311
+ content={"detail": "Authentication required"},
312
+ headers={
313
+ "WWW-Authenticate": 'Bearer realm="REM API"',
314
+ },
315
+ )
316
+
317
+ # Redirect to login for browser requests
318
+ return RedirectResponse(url="/api/auth/google/login", status_code=302)
@@ -0,0 +1,16 @@
1
+ """Authentication provider implementations."""
2
+
3
+ from .base import OAuthProvider, OAuthTokens, OAuthUserInfo
4
+ from .email import EmailAuthProvider, EmailAuthResult
5
+ from .google import GoogleOAuthProvider
6
+ from .microsoft import MicrosoftOAuthProvider
7
+
8
+ __all__ = [
9
+ "OAuthProvider",
10
+ "OAuthTokens",
11
+ "OAuthUserInfo",
12
+ "EmailAuthProvider",
13
+ "EmailAuthResult",
14
+ "GoogleOAuthProvider",
15
+ "MicrosoftOAuthProvider",
16
+ ]