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.
- rem/__init__.py +129 -0
- rem/agentic/README.md +760 -0
- rem/agentic/__init__.py +54 -0
- rem/agentic/agents/README.md +155 -0
- rem/agentic/agents/__init__.py +38 -0
- rem/agentic/agents/agent_manager.py +311 -0
- rem/agentic/agents/sse_simulator.py +502 -0
- rem/agentic/context.py +425 -0
- rem/agentic/context_builder.py +360 -0
- rem/agentic/llm_provider_models.py +301 -0
- rem/agentic/mcp/__init__.py +0 -0
- rem/agentic/mcp/tool_wrapper.py +273 -0
- rem/agentic/otel/__init__.py +5 -0
- rem/agentic/otel/setup.py +240 -0
- rem/agentic/providers/phoenix.py +926 -0
- rem/agentic/providers/pydantic_ai.py +854 -0
- rem/agentic/query.py +117 -0
- rem/agentic/query_helper.py +89 -0
- rem/agentic/schema.py +737 -0
- rem/agentic/serialization.py +245 -0
- rem/agentic/tools/__init__.py +5 -0
- rem/agentic/tools/rem_tools.py +242 -0
- rem/api/README.md +657 -0
- rem/api/deps.py +253 -0
- rem/api/main.py +460 -0
- rem/api/mcp_router/prompts.py +182 -0
- rem/api/mcp_router/resources.py +820 -0
- rem/api/mcp_router/server.py +243 -0
- rem/api/mcp_router/tools.py +1605 -0
- rem/api/middleware/tracking.py +172 -0
- rem/api/routers/admin.py +520 -0
- rem/api/routers/auth.py +898 -0
- rem/api/routers/chat/__init__.py +5 -0
- rem/api/routers/chat/child_streaming.py +394 -0
- rem/api/routers/chat/completions.py +702 -0
- rem/api/routers/chat/json_utils.py +76 -0
- rem/api/routers/chat/models.py +202 -0
- rem/api/routers/chat/otel_utils.py +33 -0
- rem/api/routers/chat/sse_events.py +546 -0
- rem/api/routers/chat/streaming.py +950 -0
- rem/api/routers/chat/streaming_utils.py +327 -0
- rem/api/routers/common.py +18 -0
- rem/api/routers/dev.py +87 -0
- rem/api/routers/feedback.py +276 -0
- rem/api/routers/messages.py +620 -0
- rem/api/routers/models.py +86 -0
- rem/api/routers/query.py +362 -0
- rem/api/routers/shared_sessions.py +422 -0
- rem/auth/README.md +258 -0
- rem/auth/__init__.py +36 -0
- rem/auth/jwt.py +367 -0
- rem/auth/middleware.py +318 -0
- rem/auth/providers/__init__.py +16 -0
- rem/auth/providers/base.py +376 -0
- rem/auth/providers/email.py +215 -0
- rem/auth/providers/google.py +163 -0
- rem/auth/providers/microsoft.py +237 -0
- rem/cli/README.md +517 -0
- rem/cli/__init__.py +8 -0
- rem/cli/commands/README.md +299 -0
- rem/cli/commands/__init__.py +3 -0
- rem/cli/commands/ask.py +549 -0
- rem/cli/commands/cluster.py +1808 -0
- rem/cli/commands/configure.py +495 -0
- rem/cli/commands/db.py +828 -0
- rem/cli/commands/dreaming.py +324 -0
- rem/cli/commands/experiments.py +1698 -0
- rem/cli/commands/mcp.py +66 -0
- rem/cli/commands/process.py +388 -0
- rem/cli/commands/query.py +109 -0
- rem/cli/commands/scaffold.py +47 -0
- rem/cli/commands/schema.py +230 -0
- rem/cli/commands/serve.py +106 -0
- rem/cli/commands/session.py +453 -0
- rem/cli/dreaming.py +363 -0
- rem/cli/main.py +123 -0
- rem/config.py +244 -0
- rem/mcp_server.py +41 -0
- rem/models/core/__init__.py +49 -0
- rem/models/core/core_model.py +70 -0
- rem/models/core/engram.py +333 -0
- rem/models/core/experiment.py +672 -0
- rem/models/core/inline_edge.py +132 -0
- rem/models/core/rem_query.py +246 -0
- rem/models/entities/__init__.py +68 -0
- rem/models/entities/domain_resource.py +38 -0
- rem/models/entities/feedback.py +123 -0
- rem/models/entities/file.py +57 -0
- rem/models/entities/image_resource.py +88 -0
- rem/models/entities/message.py +64 -0
- rem/models/entities/moment.py +123 -0
- rem/models/entities/ontology.py +181 -0
- rem/models/entities/ontology_config.py +131 -0
- rem/models/entities/resource.py +95 -0
- rem/models/entities/schema.py +87 -0
- rem/models/entities/session.py +84 -0
- rem/models/entities/shared_session.py +180 -0
- rem/models/entities/subscriber.py +175 -0
- rem/models/entities/user.py +93 -0
- rem/py.typed +0 -0
- rem/registry.py +373 -0
- rem/schemas/README.md +507 -0
- rem/schemas/__init__.py +6 -0
- rem/schemas/agents/README.md +92 -0
- rem/schemas/agents/core/agent-builder.yaml +235 -0
- rem/schemas/agents/core/moment-builder.yaml +178 -0
- rem/schemas/agents/core/rem-query-agent.yaml +226 -0
- rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
- rem/schemas/agents/core/simple-assistant.yaml +19 -0
- rem/schemas/agents/core/user-profile-builder.yaml +163 -0
- rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
- rem/schemas/agents/examples/contract-extractor.yaml +134 -0
- rem/schemas/agents/examples/cv-parser.yaml +263 -0
- rem/schemas/agents/examples/hello-world.yaml +37 -0
- rem/schemas/agents/examples/query.yaml +54 -0
- rem/schemas/agents/examples/simple.yaml +21 -0
- rem/schemas/agents/examples/test.yaml +29 -0
- rem/schemas/agents/rem.yaml +132 -0
- rem/schemas/evaluators/hello-world/default.yaml +77 -0
- rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
- rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
- rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
- rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
- rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
- rem/services/__init__.py +18 -0
- rem/services/audio/INTEGRATION.md +308 -0
- rem/services/audio/README.md +376 -0
- rem/services/audio/__init__.py +15 -0
- rem/services/audio/chunker.py +354 -0
- rem/services/audio/transcriber.py +259 -0
- rem/services/content/README.md +1269 -0
- rem/services/content/__init__.py +5 -0
- rem/services/content/providers.py +760 -0
- rem/services/content/service.py +762 -0
- rem/services/dreaming/README.md +230 -0
- rem/services/dreaming/__init__.py +53 -0
- rem/services/dreaming/affinity_service.py +322 -0
- rem/services/dreaming/moment_service.py +251 -0
- rem/services/dreaming/ontology_service.py +54 -0
- rem/services/dreaming/user_model_service.py +297 -0
- rem/services/dreaming/utils.py +39 -0
- rem/services/email/__init__.py +10 -0
- rem/services/email/service.py +522 -0
- rem/services/email/templates.py +360 -0
- rem/services/embeddings/__init__.py +11 -0
- rem/services/embeddings/api.py +127 -0
- rem/services/embeddings/worker.py +435 -0
- rem/services/fs/README.md +662 -0
- rem/services/fs/__init__.py +62 -0
- rem/services/fs/examples.py +206 -0
- rem/services/fs/examples_paths.py +204 -0
- rem/services/fs/git_provider.py +935 -0
- rem/services/fs/local_provider.py +760 -0
- rem/services/fs/parsing-hooks-examples.md +172 -0
- rem/services/fs/paths.py +276 -0
- rem/services/fs/provider.py +460 -0
- rem/services/fs/s3_provider.py +1042 -0
- rem/services/fs/service.py +186 -0
- rem/services/git/README.md +1075 -0
- rem/services/git/__init__.py +17 -0
- rem/services/git/service.py +469 -0
- rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
- rem/services/phoenix/README.md +453 -0
- rem/services/phoenix/__init__.py +46 -0
- rem/services/phoenix/client.py +960 -0
- rem/services/phoenix/config.py +88 -0
- rem/services/phoenix/prompt_labels.py +477 -0
- rem/services/postgres/README.md +757 -0
- rem/services/postgres/__init__.py +49 -0
- rem/services/postgres/diff_service.py +599 -0
- rem/services/postgres/migration_service.py +427 -0
- rem/services/postgres/programmable_diff_service.py +635 -0
- rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
- rem/services/postgres/register_type.py +353 -0
- rem/services/postgres/repository.py +481 -0
- rem/services/postgres/schema_generator.py +661 -0
- rem/services/postgres/service.py +802 -0
- rem/services/postgres/sql_builder.py +355 -0
- rem/services/rate_limit.py +113 -0
- rem/services/rem/README.md +318 -0
- rem/services/rem/__init__.py +23 -0
- rem/services/rem/exceptions.py +71 -0
- rem/services/rem/executor.py +293 -0
- rem/services/rem/parser.py +180 -0
- rem/services/rem/queries.py +196 -0
- rem/services/rem/query.py +371 -0
- rem/services/rem/service.py +608 -0
- rem/services/session/README.md +374 -0
- rem/services/session/__init__.py +13 -0
- rem/services/session/compression.py +488 -0
- rem/services/session/pydantic_messages.py +310 -0
- rem/services/session/reload.py +85 -0
- rem/services/user_service.py +130 -0
- rem/settings.py +1877 -0
- rem/sql/background_indexes.sql +52 -0
- rem/sql/migrations/001_install.sql +983 -0
- rem/sql/migrations/002_install_models.sql +3157 -0
- rem/sql/migrations/003_optional_extensions.sql +326 -0
- rem/sql/migrations/004_cache_system.sql +282 -0
- rem/sql/migrations/005_schema_update.sql +145 -0
- rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
- rem/utils/AGENTIC_CHUNKING.md +597 -0
- rem/utils/README.md +628 -0
- rem/utils/__init__.py +61 -0
- rem/utils/agentic_chunking.py +622 -0
- rem/utils/batch_ops.py +343 -0
- rem/utils/chunking.py +108 -0
- rem/utils/clip_embeddings.py +276 -0
- rem/utils/constants.py +97 -0
- rem/utils/date_utils.py +228 -0
- rem/utils/dict_utils.py +98 -0
- rem/utils/embeddings.py +436 -0
- rem/utils/examples/embeddings_example.py +305 -0
- rem/utils/examples/sql_types_example.py +202 -0
- rem/utils/files.py +323 -0
- rem/utils/markdown.py +16 -0
- rem/utils/mime_types.py +158 -0
- rem/utils/model_helpers.py +492 -0
- rem/utils/schema_loader.py +649 -0
- rem/utils/sql_paths.py +146 -0
- rem/utils/sql_types.py +350 -0
- rem/utils/user_id.py +81 -0
- rem/utils/vision.py +325 -0
- rem/workers/README.md +506 -0
- rem/workers/__init__.py +7 -0
- rem/workers/db_listener.py +579 -0
- rem/workers/db_maintainer.py +74 -0
- rem/workers/dreaming.py +502 -0
- rem/workers/engram_processor.py +312 -0
- rem/workers/sqs_file_processor.py +193 -0
- rem/workers/unlogged_maintainer.py +463 -0
- remdb-0.3.242.dist-info/METADATA +1632 -0
- remdb-0.3.242.dist-info/RECORD +235 -0
- remdb-0.3.242.dist-info/WHEEL +4 -0
- 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
|
+
]
|