@weirdfingers/baseboards 0.9.6 → 0.9.7
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.
- package/dist/index.js +560 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -5
- package/templates/README.md +0 -122
- package/templates/api/.env.example +0 -65
- package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +0 -148
- package/templates/api/Dockerfile +0 -32
- package/templates/api/README.md +0 -264
- package/templates/api/alembic/env.py +0 -114
- package/templates/api/alembic/script.py.mako +0 -28
- package/templates/api/alembic/versions/20250101_000000_initial_schema.py +0 -506
- package/templates/api/alembic/versions/20251022_174729_remove_provider_name_from_generations.py +0 -75
- package/templates/api/alembic/versions/20251023_165852_switch_to_declarative_base_and_mapping.py +0 -467
- package/templates/api/alembic/versions/20251202_000000_add_artifact_lineage.py +0 -134
- package/templates/api/alembic/versions/2025925_62735_add_seed_data_for_default_tenant.py +0 -88
- package/templates/api/alembic.ini +0 -36
- package/templates/api/config/generators.yaml +0 -237
- package/templates/api/config/storage_config.yaml +0 -26
- package/templates/api/docs/ADDING_GENERATORS.md +0 -409
- package/templates/api/docs/GENERATORS_API.md +0 -502
- package/templates/api/docs/MIGRATIONS.md +0 -472
- package/templates/api/docs/TESTING_LIVE_APIS.md +0 -417
- package/templates/api/docs/storage_providers.md +0 -337
- package/templates/api/pyproject.toml +0 -205
- package/templates/api/src/boards/__init__.py +0 -10
- package/templates/api/src/boards/api/app.py +0 -172
- package/templates/api/src/boards/api/auth.py +0 -75
- package/templates/api/src/boards/api/endpoints/__init__.py +0 -3
- package/templates/api/src/boards/api/endpoints/jobs.py +0 -76
- package/templates/api/src/boards/api/endpoints/setup.py +0 -505
- package/templates/api/src/boards/api/endpoints/sse.py +0 -129
- package/templates/api/src/boards/api/endpoints/storage.py +0 -155
- package/templates/api/src/boards/api/endpoints/tenant_registration.py +0 -296
- package/templates/api/src/boards/api/endpoints/uploads.py +0 -149
- package/templates/api/src/boards/api/endpoints/webhooks.py +0 -13
- package/templates/api/src/boards/auth/__init__.py +0 -15
- package/templates/api/src/boards/auth/adapters/__init__.py +0 -27
- package/templates/api/src/boards/auth/adapters/auth0.py +0 -220
- package/templates/api/src/boards/auth/adapters/base.py +0 -73
- package/templates/api/src/boards/auth/adapters/clerk.py +0 -172
- package/templates/api/src/boards/auth/adapters/jwt.py +0 -122
- package/templates/api/src/boards/auth/adapters/none.py +0 -102
- package/templates/api/src/boards/auth/adapters/oidc.py +0 -284
- package/templates/api/src/boards/auth/adapters/supabase.py +0 -110
- package/templates/api/src/boards/auth/context.py +0 -35
- package/templates/api/src/boards/auth/factory.py +0 -129
- package/templates/api/src/boards/auth/middleware.py +0 -221
- package/templates/api/src/boards/auth/provisioning.py +0 -129
- package/templates/api/src/boards/auth/tenant_extraction.py +0 -278
- package/templates/api/src/boards/cli.py +0 -354
- package/templates/api/src/boards/config.py +0 -131
- package/templates/api/src/boards/database/__init__.py +0 -7
- package/templates/api/src/boards/database/cli.py +0 -110
- package/templates/api/src/boards/database/connection.py +0 -292
- package/templates/api/src/boards/database/models.py +0 -19
- package/templates/api/src/boards/database/seed_data.py +0 -182
- package/templates/api/src/boards/dbmodels/__init__.py +0 -441
- package/templates/api/src/boards/generators/__init__.py +0 -57
- package/templates/api/src/boards/generators/artifact_resolution.py +0 -405
- package/templates/api/src/boards/generators/artifacts.py +0 -53
- package/templates/api/src/boards/generators/base.py +0 -144
- package/templates/api/src/boards/generators/implementations/__init__.py +0 -14
- package/templates/api/src/boards/generators/implementations/fal/__init__.py +0 -25
- package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +0 -23
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_music_generation.py +0 -171
- package/templates/api/src/boards/generators/implementations/fal/audio/beatoven_sound_effect_generation.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_text_to_speech.py +0 -176
- package/templates/api/src/boards/generators/implementations/fal/audio/chatterbox_tts_turbo.py +0 -195
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_sound_effects_v2.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/audio/elevenlabs_tts_eleven_v3.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_elevenlabs_tts_turbo_v2_5.py +0 -206
- package/templates/api/src/boards/generators/implementations/fal/audio/fal_minimax_speech_26_hd.py +0 -237
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +0 -63
- package/templates/api/src/boards/generators/implementations/fal/image/bytedance_seedream_v45_edit.py +0 -219
- package/templates/api/src/boards/generators/implementations/fal/image/clarity_upscaler.py +0 -220
- package/templates/api/src/boards/generators/implementations/fal/image/crystal_upscaler.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/image/fal_ideogram_character.py +0 -227
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2.py +0 -203
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_edit.py +0 -230
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro.py +0 -204
- package/templates/api/src/boards/generators/implementations/fal/image/flux_2_pro_edit.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gemini_25_flash_image_edit.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_15_edit.py +0 -216
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_5.py +0 -177
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_edit_image.py +0 -182
- package/templates/api/src/boards/generators/implementations/fal/image/gpt_image_1_mini.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_character_edit.py +0 -299
- package/templates/api/src/boards/generators/implementations/fal/image/ideogram_v2.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro.py +0 -179
- package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_pro_edit.py +0 -226
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image.py +0 -249
- package/templates/api/src/boards/generators/implementations/fal/image/qwen_image_edit.py +0 -244
- package/templates/api/src/boards/generators/implementations/fal/image/reve_edit.py +0 -178
- package/templates/api/src/boards/generators/implementations/fal/image/reve_text_to_image.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/image/seedream_v45_text_to_image.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/utils.py +0 -61
- package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +0 -77
- package/templates/api/src/boards/generators/implementations/fal/video/bytedance_seedance_v1_pro_text_to_video.py +0 -209
- package/templates/api/src/boards/generators/implementations/fal/video/creatify_lipsync.py +0 -161
- package/templates/api/src/boards/generators/implementations/fal/video/fal_bytedance_seedance_v1_pro_image_to_video.py +0 -222
- package/templates/api/src/boards/generators/implementations/fal/video/fal_minimax_hailuo_02_standard_text_to_video.py +0 -152
- package/templates/api/src/boards/generators/implementations/fal/video/fal_pixverse_lipsync.py +0 -197
- package/templates/api/src/boards/generators/implementations/fal/video/fal_sora_2_text_to_video.py +0 -173
- package/templates/api/src/boards/generators/implementations/fal/video/infinitalk.py +0 -221
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_pro.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_ai_avatar_v2_standard.py +0 -159
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_image_to_video.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +0 -168
- package/templates/api/src/boards/generators/implementations/fal/video/minimax_hailuo_2_3_pro_image_to_video.py +0 -153
- package/templates/api/src/boards/generators/implementations/fal/video/sora2_image_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_image_to_video_pro.py +0 -175
- package/templates/api/src/boards/generators/implementations/fal/video/sora_2_text_to_video_pro.py +0 -163
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +0 -167
- package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2_pro.py +0 -155
- package/templates/api/src/boards/generators/implementations/fal/video/veed_fabric_1_0.py +0 -180
- package/templates/api/src/boards/generators/implementations/fal/video/veed_lipsync.py +0 -174
- package/templates/api/src/boards/generators/implementations/fal/video/veo3.py +0 -194
- package/templates/api/src/boards/generators/implementations/fal/video/veo31.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast.py +0 -190
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_fast_image_to_video.py +0 -191
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +0 -187
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_image_to_video.py +0 -183
- package/templates/api/src/boards/generators/implementations/fal/video/veo31_reference_to_video.py +0 -172
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_image_to_video.py +0 -212
- package/templates/api/src/boards/generators/implementations/fal/video/wan_25_preview_text_to_video.py +0 -208
- package/templates/api/src/boards/generators/implementations/fal/video/wan_pro_image_to_video.py +0 -158
- package/templates/api/src/boards/generators/implementations/kie/__init__.py +0 -11
- package/templates/api/src/boards/generators/implementations/kie/base.py +0 -316
- package/templates/api/src/boards/generators/implementations/kie/image/__init__.py +0 -3
- package/templates/api/src/boards/generators/implementations/kie/image/nano_banana_edit.py +0 -190
- package/templates/api/src/boards/generators/implementations/kie/utils.py +0 -98
- package/templates/api/src/boards/generators/implementations/kie/video/__init__.py +0 -8
- package/templates/api/src/boards/generators/implementations/kie/video/veo3.py +0 -161
- package/templates/api/src/boards/generators/implementations/openai/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/audio/whisper.py +0 -69
- package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/openai/image/dalle3.py +0 -96
- package/templates/api/src/boards/generators/implementations/replicate/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/image/flux_pro.py +0 -88
- package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +0 -1
- package/templates/api/src/boards/generators/implementations/replicate/video/lipsync.py +0 -73
- package/templates/api/src/boards/generators/loader.py +0 -253
- package/templates/api/src/boards/generators/registry.py +0 -114
- package/templates/api/src/boards/generators/resolution.py +0 -632
- package/templates/api/src/boards/generators/testmods/class_gen.py +0 -34
- package/templates/api/src/boards/generators/testmods/import_side_effect.py +0 -35
- package/templates/api/src/boards/graphql/__init__.py +0 -7
- package/templates/api/src/boards/graphql/access_control.py +0 -136
- package/templates/api/src/boards/graphql/mutations/root.py +0 -148
- package/templates/api/src/boards/graphql/queries/root.py +0 -116
- package/templates/api/src/boards/graphql/resolvers/__init__.py +0 -8
- package/templates/api/src/boards/graphql/resolvers/auth.py +0 -12
- package/templates/api/src/boards/graphql/resolvers/board.py +0 -1053
- package/templates/api/src/boards/graphql/resolvers/generation.py +0 -666
- package/templates/api/src/boards/graphql/resolvers/generator.py +0 -50
- package/templates/api/src/boards/graphql/resolvers/lineage.py +0 -381
- package/templates/api/src/boards/graphql/resolvers/upload.py +0 -463
- package/templates/api/src/boards/graphql/resolvers/user.py +0 -25
- package/templates/api/src/boards/graphql/schema.py +0 -81
- package/templates/api/src/boards/graphql/types/board.py +0 -102
- package/templates/api/src/boards/graphql/types/generation.py +0 -166
- package/templates/api/src/boards/graphql/types/generator.py +0 -17
- package/templates/api/src/boards/graphql/types/user.py +0 -47
- package/templates/api/src/boards/jobs/repository.py +0 -153
- package/templates/api/src/boards/logging.py +0 -195
- package/templates/api/src/boards/middleware.py +0 -339
- package/templates/api/src/boards/progress/__init__.py +0 -4
- package/templates/api/src/boards/progress/models.py +0 -25
- package/templates/api/src/boards/progress/publisher.py +0 -64
- package/templates/api/src/boards/py.typed +0 -0
- package/templates/api/src/boards/redis_pool.py +0 -118
- package/templates/api/src/boards/storage/__init__.py +0 -52
- package/templates/api/src/boards/storage/base.py +0 -363
- package/templates/api/src/boards/storage/config.py +0 -187
- package/templates/api/src/boards/storage/factory.py +0 -288
- package/templates/api/src/boards/storage/implementations/__init__.py +0 -27
- package/templates/api/src/boards/storage/implementations/gcs.py +0 -340
- package/templates/api/src/boards/storage/implementations/local.py +0 -201
- package/templates/api/src/boards/storage/implementations/s3.py +0 -294
- package/templates/api/src/boards/storage/implementations/supabase.py +0 -218
- package/templates/api/src/boards/tenant_isolation.py +0 -446
- package/templates/api/src/boards/validation.py +0 -262
- package/templates/api/src/boards/workers/__init__.py +0 -1
- package/templates/api/src/boards/workers/actors.py +0 -274
- package/templates/api/src/boards/workers/cli.py +0 -125
- package/templates/api/src/boards/workers/context.py +0 -348
- package/templates/api/src/boards/workers/middleware.py +0 -58
- package/templates/api/src/py.typed +0 -0
- package/templates/compose.web.yaml +0 -35
- package/templates/compose.yaml +0 -116
- package/templates/docker/env.example +0 -23
- package/templates/web/.env.example +0 -28
- package/templates/web/Dockerfile +0 -51
- package/templates/web/components.json +0 -22
- package/templates/web/imageLoader.js +0 -18
- package/templates/web/next-env.d.ts +0 -5
- package/templates/web/next.config.js +0 -36
- package/templates/web/package.json +0 -41
- package/templates/web/postcss.config.mjs +0 -7
- package/templates/web/public/favicon.ico +0 -0
- package/templates/web/src/app/boards/[boardId]/page.tsx +0 -353
- package/templates/web/src/app/globals.css +0 -123
- package/templates/web/src/app/layout.tsx +0 -31
- package/templates/web/src/app/lineage/[generationId]/page.tsx +0 -235
- package/templates/web/src/app/page.tsx +0 -35
- package/templates/web/src/app/providers.tsx +0 -18
- package/templates/web/src/components/boards/ArtifactInputSlots.tsx +0 -206
- package/templates/web/src/components/boards/ArtifactPreview.tsx +0 -466
- package/templates/web/src/components/boards/GenerationGrid.tsx +0 -282
- package/templates/web/src/components/boards/GenerationInput.tsx +0 -370
- package/templates/web/src/components/boards/GeneratorSelector.tsx +0 -272
- package/templates/web/src/components/boards/UploadArtifact.tsx +0 -563
- package/templates/web/src/components/header.tsx +0 -32
- package/templates/web/src/components/theme-provider.tsx +0 -10
- package/templates/web/src/components/theme-toggle.tsx +0 -75
- package/templates/web/src/components/ui/alert-dialog.tsx +0 -157
- package/templates/web/src/components/ui/button.tsx +0 -58
- package/templates/web/src/components/ui/card.tsx +0 -92
- package/templates/web/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/web/src/components/ui/navigation-menu.tsx +0 -168
- package/templates/web/src/components/ui/toast.tsx +0 -128
- package/templates/web/src/components/ui/toaster.tsx +0 -35
- package/templates/web/src/components/ui/use-toast.ts +0 -187
- package/templates/web/src/hooks/useGeneratorMRU.ts +0 -57
- package/templates/web/src/lib/utils.ts +0 -6
- package/templates/web/tsconfig.json +0 -41
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
"""Authentication middleware for FastAPI."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from uuid import UUID
|
|
6
|
-
|
|
7
|
-
from fastapi import Header, HTTPException
|
|
8
|
-
|
|
9
|
-
from ..database.connection import get_async_session
|
|
10
|
-
from ..database.seed_data import ensure_tenant
|
|
11
|
-
from ..logging import get_logger
|
|
12
|
-
from .adapters.base import AuthenticationError
|
|
13
|
-
from .context import DEFAULT_TENANT_UUID, AuthContext
|
|
14
|
-
from .factory import get_auth_adapter_cached
|
|
15
|
-
from .provisioning import ensure_local_user
|
|
16
|
-
from .tenant_extraction import extract_tenant_from_claims
|
|
17
|
-
|
|
18
|
-
logger = get_logger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
async def get_auth_context(
|
|
22
|
-
authorization: str | None = Header(None),
|
|
23
|
-
x_tenant: str | None = Header(None, alias="X-Tenant"),
|
|
24
|
-
) -> AuthContext:
|
|
25
|
-
"""
|
|
26
|
-
Extract authentication context from request headers.
|
|
27
|
-
|
|
28
|
-
This function:
|
|
29
|
-
1. Extracts Bearer token from Authorization header
|
|
30
|
-
2. Verifies token using the configured auth adapter
|
|
31
|
-
3. Resolves tenant (defaults to 'default' for single-tenant)
|
|
32
|
-
4. Performs JIT user provisioning
|
|
33
|
-
5. Returns AuthContext for the request
|
|
34
|
-
|
|
35
|
-
For no-auth mode, any token (or "dev-token") will work.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
authorization: Authorization header (Bearer token)
|
|
39
|
-
x_tenant: Tenant identifier header
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
AuthContext with user, tenant, and token info
|
|
43
|
-
"""
|
|
44
|
-
adapter = get_auth_adapter_cached()
|
|
45
|
-
|
|
46
|
-
# Check if we're in no-auth mode
|
|
47
|
-
is_no_auth_mode = hasattr(adapter, "default_user_id") # NoAuthAdapter has this attribute
|
|
48
|
-
|
|
49
|
-
# Handle unauthenticated requests
|
|
50
|
-
if not authorization:
|
|
51
|
-
if is_no_auth_mode:
|
|
52
|
-
# In no-auth mode, create a default token
|
|
53
|
-
authorization = "Bearer dev-token"
|
|
54
|
-
else:
|
|
55
|
-
# Use header tenant or default for unauthenticated requests
|
|
56
|
-
tenant_slug = x_tenant or "default"
|
|
57
|
-
tenant_uuid = await _resolve_tenant_uuid(tenant_slug)
|
|
58
|
-
return AuthContext(
|
|
59
|
-
user_id=None,
|
|
60
|
-
tenant_id=tenant_uuid,
|
|
61
|
-
principal=None,
|
|
62
|
-
token=None,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
# Extract Bearer token
|
|
66
|
-
if not authorization.startswith("Bearer "):
|
|
67
|
-
logger.warning("Invalid authorization format received")
|
|
68
|
-
raise HTTPException(
|
|
69
|
-
status_code=401,
|
|
70
|
-
detail="Invalid authorization format. Expected: Bearer <token>",
|
|
71
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
token = authorization[7:] # Remove "Bearer " prefix
|
|
75
|
-
|
|
76
|
-
if not token:
|
|
77
|
-
logger.warning("Empty token provided")
|
|
78
|
-
raise HTTPException(
|
|
79
|
-
status_code=401,
|
|
80
|
-
detail="Empty token",
|
|
81
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
try:
|
|
85
|
-
# Verify token with auth adapter
|
|
86
|
-
principal = await adapter.verify_token(token)
|
|
87
|
-
|
|
88
|
-
# Extract tenant from JWT/OIDC claims with fallback to header
|
|
89
|
-
tenant_slug = extract_tenant_from_claims(principal, fallback_tenant=x_tenant)
|
|
90
|
-
|
|
91
|
-
logger.debug(
|
|
92
|
-
"Tenant resolved for authenticated request",
|
|
93
|
-
tenant_slug=tenant_slug,
|
|
94
|
-
header_tenant=x_tenant,
|
|
95
|
-
provider=principal.get("provider"),
|
|
96
|
-
subject=principal.get("subject"),
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Resolve tenant slug to UUID and perform JIT user provisioning
|
|
100
|
-
try:
|
|
101
|
-
async with get_async_session() as db:
|
|
102
|
-
# Ensure tenant exists and get its UUID
|
|
103
|
-
tenant_uuid = await ensure_tenant(db, slug=tenant_slug)
|
|
104
|
-
# Now provision the user with the tenant UUID
|
|
105
|
-
user_id = await ensure_local_user(db, tenant_uuid, principal)
|
|
106
|
-
|
|
107
|
-
logger.debug(
|
|
108
|
-
"User provisioned and tenant resolved",
|
|
109
|
-
user_id=str(user_id),
|
|
110
|
-
tenant_uuid=str(tenant_uuid),
|
|
111
|
-
tenant_slug=tenant_slug,
|
|
112
|
-
)
|
|
113
|
-
except Exception as db_error:
|
|
114
|
-
# Database connection failed, use the same deterministic fallback
|
|
115
|
-
logger.error(
|
|
116
|
-
"Database connection failed, using fallback user ID generation",
|
|
117
|
-
error=str(db_error),
|
|
118
|
-
principal_provider=principal.get("provider"),
|
|
119
|
-
principal_subject=principal.get("subject"),
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
import hashlib
|
|
123
|
-
|
|
124
|
-
provider = principal.get("provider", "unknown")
|
|
125
|
-
subject = principal.get("subject", "anonymous")
|
|
126
|
-
stable_input = f"{provider}:{subject}:{tenant_slug}"
|
|
127
|
-
user_id_hash = hashlib.sha256(stable_input.encode()).hexdigest()[:32]
|
|
128
|
-
# Format hash as UUID: 8-4-4-4-12 pattern
|
|
129
|
-
formatted_uuid = (
|
|
130
|
-
f"{user_id_hash[:8]}-{user_id_hash[8:12]}-"
|
|
131
|
-
f"{user_id_hash[12:16]}-{user_id_hash[16:20]}-"
|
|
132
|
-
f"{user_id_hash[20:32]}"
|
|
133
|
-
)
|
|
134
|
-
from uuid import UUID
|
|
135
|
-
|
|
136
|
-
user_id = UUID(formatted_uuid)
|
|
137
|
-
|
|
138
|
-
# Also resolve tenant_uuid in this fallback path
|
|
139
|
-
tenant_uuid = await _resolve_tenant_uuid(tenant_slug)
|
|
140
|
-
|
|
141
|
-
return AuthContext(
|
|
142
|
-
user_id=user_id,
|
|
143
|
-
tenant_id=tenant_uuid,
|
|
144
|
-
principal=principal,
|
|
145
|
-
token=token,
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
except AuthenticationError as e:
|
|
149
|
-
logger.warning("Authentication failed", error=str(e))
|
|
150
|
-
raise HTTPException(
|
|
151
|
-
status_code=401,
|
|
152
|
-
detail=str(e),
|
|
153
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
154
|
-
) from e
|
|
155
|
-
except Exception as e:
|
|
156
|
-
logger.error("Unexpected authentication error", error=str(e))
|
|
157
|
-
raise HTTPException(
|
|
158
|
-
status_code=401,
|
|
159
|
-
detail="Authentication failed",
|
|
160
|
-
headers={"WWW-Authenticate": "Bearer"},
|
|
161
|
-
) from e
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
async def get_auth_context_optional(
|
|
165
|
-
authorization: str | None = Header(None),
|
|
166
|
-
x_tenant: str | None = Header(None, alias="X-Tenant"),
|
|
167
|
-
) -> AuthContext:
|
|
168
|
-
"""
|
|
169
|
-
Optional authentication - returns unauthenticated context if no token.
|
|
170
|
-
|
|
171
|
-
Use this for endpoints that work both authenticated and unauthenticated.
|
|
172
|
-
"""
|
|
173
|
-
try:
|
|
174
|
-
return await get_auth_context(authorization, x_tenant)
|
|
175
|
-
except HTTPException:
|
|
176
|
-
# Return unauthenticated context
|
|
177
|
-
tenant_slug = x_tenant or "default"
|
|
178
|
-
tenant_uuid = await _resolve_tenant_uuid(tenant_slug)
|
|
179
|
-
return AuthContext(
|
|
180
|
-
user_id=None,
|
|
181
|
-
tenant_id=tenant_uuid,
|
|
182
|
-
principal=None,
|
|
183
|
-
token=None,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
async def _resolve_tenant_uuid(tenant_slug: str) -> UUID:
|
|
188
|
-
"""
|
|
189
|
-
Resolve a tenant slug to its UUID.
|
|
190
|
-
|
|
191
|
-
Falls back to DEFAULT_TENANT_UUID if:
|
|
192
|
-
- Database lookup fails
|
|
193
|
-
- Tenant doesn't exist
|
|
194
|
-
- Running in single-tenant mode
|
|
195
|
-
|
|
196
|
-
Args:
|
|
197
|
-
tenant_slug: The tenant slug to resolve (e.g., "default", "acme-corp")
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
UUID of the tenant, or DEFAULT_TENANT_UUID if resolution fails
|
|
201
|
-
"""
|
|
202
|
-
try:
|
|
203
|
-
from ..database.connection import get_async_session
|
|
204
|
-
from ..database.seed_data import ensure_tenant
|
|
205
|
-
|
|
206
|
-
async with get_async_session() as db:
|
|
207
|
-
tenant_uuid = await ensure_tenant(db, slug=tenant_slug)
|
|
208
|
-
logger.debug(
|
|
209
|
-
"Resolved tenant slug to UUID",
|
|
210
|
-
tenant_slug=tenant_slug,
|
|
211
|
-
tenant_uuid=str(tenant_uuid),
|
|
212
|
-
)
|
|
213
|
-
return tenant_uuid
|
|
214
|
-
except Exception as e:
|
|
215
|
-
logger.warning(
|
|
216
|
-
"Failed to resolve tenant UUID, using default",
|
|
217
|
-
tenant_slug=tenant_slug,
|
|
218
|
-
error=str(e),
|
|
219
|
-
default_uuid=str(DEFAULT_TENANT_UUID),
|
|
220
|
-
)
|
|
221
|
-
return DEFAULT_TENANT_UUID
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
"""User provisioning and management."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from uuid import UUID
|
|
6
|
-
|
|
7
|
-
from sqlalchemy import and_, select
|
|
8
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
9
|
-
|
|
10
|
-
from ..dbmodels import Users
|
|
11
|
-
from ..logging import get_logger
|
|
12
|
-
from .adapters.base import Principal
|
|
13
|
-
|
|
14
|
-
logger = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
async def ensure_local_user(db: AsyncSession, tenant_id: UUID, principal: Principal) -> UUID:
|
|
18
|
-
"""
|
|
19
|
-
Ensure a local user exists for the given principal (JIT provisioning).
|
|
20
|
-
|
|
21
|
-
This function creates a local user record if one doesn't exist for the
|
|
22
|
-
given (tenant_id, auth_provider, auth_subject) combination.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
db: Database session
|
|
26
|
-
tenant_id: Tenant UUID
|
|
27
|
-
principal: Authenticated principal from auth provider
|
|
28
|
-
|
|
29
|
-
Returns:
|
|
30
|
-
UUID of the local user
|
|
31
|
-
"""
|
|
32
|
-
provider = principal["provider"]
|
|
33
|
-
subject = principal["subject"]
|
|
34
|
-
|
|
35
|
-
# Ensure tenant_id is a UUID
|
|
36
|
-
if not isinstance(tenant_id, UUID):
|
|
37
|
-
raise ValueError(f"tenant_id must be a UUID, got {type(tenant_id)}")
|
|
38
|
-
|
|
39
|
-
# Try to find existing user
|
|
40
|
-
stmt = select(Users).where(
|
|
41
|
-
and_(
|
|
42
|
-
Users.tenant_id == tenant_id,
|
|
43
|
-
Users.auth_provider == provider,
|
|
44
|
-
Users.auth_subject == subject,
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
result = await db.execute(stmt)
|
|
49
|
-
user = result.scalar_one_or_none()
|
|
50
|
-
|
|
51
|
-
if user:
|
|
52
|
-
# Update user info if provided in principal, but preserve existing non-empty values
|
|
53
|
-
updated = False
|
|
54
|
-
|
|
55
|
-
email = principal.get("email")
|
|
56
|
-
if email and not user.email: # Only update if current email is empty/None
|
|
57
|
-
user.email = email
|
|
58
|
-
updated = True
|
|
59
|
-
|
|
60
|
-
display_name = principal.get("display_name")
|
|
61
|
-
# Only update if current display_name is empty/None
|
|
62
|
-
if display_name and not user.display_name:
|
|
63
|
-
user.display_name = display_name
|
|
64
|
-
updated = True
|
|
65
|
-
|
|
66
|
-
avatar_url = principal.get("avatar_url")
|
|
67
|
-
if avatar_url and not user.avatar_url: # Only update if current avatar_url is empty/None
|
|
68
|
-
user.avatar_url = avatar_url
|
|
69
|
-
updated = True
|
|
70
|
-
|
|
71
|
-
if updated:
|
|
72
|
-
await db.commit()
|
|
73
|
-
await db.refresh(user)
|
|
74
|
-
logger.info("Updated user info (preserving existing values)", user_id=str(user.id))
|
|
75
|
-
|
|
76
|
-
return user.id
|
|
77
|
-
|
|
78
|
-
# Create new user
|
|
79
|
-
user = Users(
|
|
80
|
-
tenant_id=tenant_id,
|
|
81
|
-
auth_provider=provider,
|
|
82
|
-
auth_subject=subject,
|
|
83
|
-
email=principal.get("email"),
|
|
84
|
-
display_name=principal.get("display_name"),
|
|
85
|
-
avatar_url=principal.get("avatar_url"),
|
|
86
|
-
metadata_={
|
|
87
|
-
"created_via": "jit_provisioning",
|
|
88
|
-
"provider_claims": principal.get("claims", {}),
|
|
89
|
-
},
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
db.add(user)
|
|
93
|
-
await db.commit()
|
|
94
|
-
await db.refresh(user)
|
|
95
|
-
|
|
96
|
-
logger.info(
|
|
97
|
-
"Created new user via JIT provisioning",
|
|
98
|
-
user_id=str(user.id),
|
|
99
|
-
tenant_id=tenant_id,
|
|
100
|
-
provider=provider,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return user.id
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
async def get_user_by_id(db: AsyncSession, user_id: UUID) -> Users | None:
|
|
107
|
-
"""Get a user by ID."""
|
|
108
|
-
stmt = select(Users).where(Users.id == user_id)
|
|
109
|
-
result = await db.execute(stmt)
|
|
110
|
-
return result.scalar_one_or_none()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async def get_user_by_auth_info(
|
|
114
|
-
db: AsyncSession, tenant_id: UUID, auth_provider: str, auth_subject: str
|
|
115
|
-
) -> Users | None:
|
|
116
|
-
"""Get a user by auth provider information."""
|
|
117
|
-
# Ensure tenant_id is a UUID
|
|
118
|
-
if not isinstance(tenant_id, UUID):
|
|
119
|
-
raise ValueError(f"tenant_id must be a UUID, got {type(tenant_id)}")
|
|
120
|
-
|
|
121
|
-
stmt = select(Users).where(
|
|
122
|
-
and_(
|
|
123
|
-
Users.tenant_id == tenant_id,
|
|
124
|
-
Users.auth_provider == auth_provider,
|
|
125
|
-
Users.auth_subject == auth_subject,
|
|
126
|
-
)
|
|
127
|
-
)
|
|
128
|
-
result = await db.execute(stmt)
|
|
129
|
-
return result.scalar_one_or_none()
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Tenant extraction utilities for multi-tenant authentication.
|
|
3
|
-
|
|
4
|
-
This module provides utilities to extract tenant information from JWT/OIDC claims
|
|
5
|
-
and other authentication contexts.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from ..config import settings
|
|
13
|
-
from ..logging import get_logger
|
|
14
|
-
from .context import Principal
|
|
15
|
-
|
|
16
|
-
logger = get_logger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def extract_tenant_from_claims(
|
|
20
|
-
principal: Principal,
|
|
21
|
-
fallback_tenant: str | None = None,
|
|
22
|
-
) -> str:
|
|
23
|
-
"""
|
|
24
|
-
Extract tenant slug from JWT/OIDC claims.
|
|
25
|
-
|
|
26
|
-
This function supports multiple tenant extraction strategies:
|
|
27
|
-
1. Direct 'tenant' claim in JWT
|
|
28
|
-
2. Organization-based claims (org, organization, org_slug)
|
|
29
|
-
3. Custom claims (configurable via settings)
|
|
30
|
-
4. Domain-based extraction from email
|
|
31
|
-
5. Fallback to header or default
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
principal: Principal with claims from JWT/OIDC
|
|
35
|
-
fallback_tenant: Fallback tenant slug if no tenant found in claims
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
Tenant slug extracted from claims or fallback
|
|
39
|
-
"""
|
|
40
|
-
claims = principal.get("claims", {})
|
|
41
|
-
|
|
42
|
-
if not claims:
|
|
43
|
-
logger.debug("No claims available in principal, using fallback")
|
|
44
|
-
return fallback_tenant or settings.default_tenant_slug
|
|
45
|
-
|
|
46
|
-
# Strategy 1: Direct tenant claim
|
|
47
|
-
if tenant_slug := claims.get("tenant"):
|
|
48
|
-
logger.info("Extracted tenant from 'tenant' claim", tenant_slug=tenant_slug)
|
|
49
|
-
return _validate_tenant_slug(tenant_slug)
|
|
50
|
-
|
|
51
|
-
# Strategy 2: Organization-based claims
|
|
52
|
-
org_claims = ["org", "organization", "org_slug", "org_name"]
|
|
53
|
-
for claim_name in org_claims:
|
|
54
|
-
if org_value := claims.get(claim_name):
|
|
55
|
-
tenant_slug = _normalize_tenant_slug(org_value)
|
|
56
|
-
logger.info(
|
|
57
|
-
"Extracted tenant from organization claim",
|
|
58
|
-
claim_name=claim_name,
|
|
59
|
-
org_value=org_value,
|
|
60
|
-
tenant_slug=tenant_slug,
|
|
61
|
-
)
|
|
62
|
-
return tenant_slug
|
|
63
|
-
|
|
64
|
-
# Strategy 3: Custom claims (configurable)
|
|
65
|
-
custom_tenant_claim = getattr(settings, "jwt_tenant_claim", None)
|
|
66
|
-
if custom_tenant_claim and (custom_value := claims.get(custom_tenant_claim)):
|
|
67
|
-
tenant_slug = _normalize_tenant_slug(custom_value)
|
|
68
|
-
logger.info(
|
|
69
|
-
"Extracted tenant from custom claim",
|
|
70
|
-
claim_name=custom_tenant_claim,
|
|
71
|
-
custom_value=custom_value,
|
|
72
|
-
tenant_slug=tenant_slug,
|
|
73
|
-
)
|
|
74
|
-
return tenant_slug
|
|
75
|
-
|
|
76
|
-
# Strategy 4: Domain-based extraction from email
|
|
77
|
-
if settings.multi_tenant_mode and (email := claims.get("email")):
|
|
78
|
-
tenant_slug = _extract_tenant_from_email_domain(email)
|
|
79
|
-
if tenant_slug:
|
|
80
|
-
logger.info(
|
|
81
|
-
"Extracted tenant from email domain",
|
|
82
|
-
email=email,
|
|
83
|
-
tenant_slug=tenant_slug,
|
|
84
|
-
)
|
|
85
|
-
return tenant_slug
|
|
86
|
-
|
|
87
|
-
# Strategy 5: Namespace/sub-organization claims
|
|
88
|
-
namespace_claims = ["namespace", "group", "team", "workspace"]
|
|
89
|
-
for claim_name in namespace_claims:
|
|
90
|
-
if namespace_value := claims.get(claim_name):
|
|
91
|
-
tenant_slug = _normalize_tenant_slug(namespace_value)
|
|
92
|
-
logger.info(
|
|
93
|
-
"Extracted tenant from namespace claim",
|
|
94
|
-
claim_name=claim_name,
|
|
95
|
-
namespace_value=namespace_value,
|
|
96
|
-
tenant_slug=tenant_slug,
|
|
97
|
-
)
|
|
98
|
-
return tenant_slug
|
|
99
|
-
|
|
100
|
-
# No tenant found in claims, use fallback
|
|
101
|
-
logger.debug(
|
|
102
|
-
"No tenant information found in JWT/OIDC claims",
|
|
103
|
-
available_claims=list(claims.keys()),
|
|
104
|
-
using_fallback=fallback_tenant or settings.default_tenant_slug,
|
|
105
|
-
)
|
|
106
|
-
return fallback_tenant or settings.default_tenant_slug
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def extract_tenant_from_oidc_userinfo(
|
|
110
|
-
userinfo: dict[str, Any],
|
|
111
|
-
fallback_tenant: str | None = None,
|
|
112
|
-
) -> str:
|
|
113
|
-
"""
|
|
114
|
-
Extract tenant slug from OIDC userinfo endpoint response.
|
|
115
|
-
|
|
116
|
-
Args:
|
|
117
|
-
userinfo: Response from OIDC userinfo endpoint
|
|
118
|
-
fallback_tenant: Fallback tenant slug if no tenant found
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
Tenant slug extracted from userinfo or fallback
|
|
122
|
-
"""
|
|
123
|
-
if not userinfo:
|
|
124
|
-
return fallback_tenant or settings.default_tenant_slug
|
|
125
|
-
|
|
126
|
-
# Similar extraction strategies as claims
|
|
127
|
-
if tenant_slug := userinfo.get("tenant"):
|
|
128
|
-
return _validate_tenant_slug(tenant_slug)
|
|
129
|
-
|
|
130
|
-
# Organization-based extraction
|
|
131
|
-
for org_field in ["organization", "org", "company"]:
|
|
132
|
-
if org_value := userinfo.get(org_field):
|
|
133
|
-
return _normalize_tenant_slug(org_value)
|
|
134
|
-
|
|
135
|
-
# Groups/roles extraction
|
|
136
|
-
if groups := userinfo.get("groups", []):
|
|
137
|
-
if isinstance(groups, list) and groups:
|
|
138
|
-
# Use first group as tenant
|
|
139
|
-
return _normalize_tenant_slug(groups[0])
|
|
140
|
-
|
|
141
|
-
return fallback_tenant or settings.default_tenant_slug
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _normalize_tenant_slug(value: Any) -> str:
|
|
145
|
-
"""
|
|
146
|
-
Normalize a value to a valid tenant slug.
|
|
147
|
-
|
|
148
|
-
Args:
|
|
149
|
-
value: Value to normalize (string, dict, etc.)
|
|
150
|
-
|
|
151
|
-
Returns:
|
|
152
|
-
Normalized tenant slug
|
|
153
|
-
"""
|
|
154
|
-
if isinstance(value, dict):
|
|
155
|
-
# Extract slug if it's an object with slug/id fields
|
|
156
|
-
return _normalize_tenant_slug(value.get("slug") or value.get("id") or value.get("name", ""))
|
|
157
|
-
|
|
158
|
-
# Convert to string and normalize
|
|
159
|
-
slug = str(value).lower().strip()
|
|
160
|
-
|
|
161
|
-
# Replace spaces and invalid characters with hyphens
|
|
162
|
-
import re
|
|
163
|
-
|
|
164
|
-
slug = re.sub(r"[^a-z0-9-]", "-", slug)
|
|
165
|
-
|
|
166
|
-
# Remove multiple consecutive hyphens
|
|
167
|
-
slug = re.sub(r"-+", "-", slug)
|
|
168
|
-
|
|
169
|
-
# Remove leading/trailing hyphens
|
|
170
|
-
slug = slug.strip("-")
|
|
171
|
-
|
|
172
|
-
# Ensure it's not empty and not too long
|
|
173
|
-
if not slug:
|
|
174
|
-
slug = "unknown"
|
|
175
|
-
elif len(slug) > 50: # Reasonable limit for tenant slugs
|
|
176
|
-
slug = slug[:50].rstrip("-")
|
|
177
|
-
|
|
178
|
-
return slug
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _validate_tenant_slug(slug: str) -> str:
|
|
182
|
-
"""
|
|
183
|
-
Validate a tenant slug format.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
slug: Tenant slug to validate
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
Validated slug
|
|
190
|
-
|
|
191
|
-
Raises:
|
|
192
|
-
ValueError: If slug is invalid
|
|
193
|
-
"""
|
|
194
|
-
if not slug:
|
|
195
|
-
raise ValueError("Tenant slug cannot be empty")
|
|
196
|
-
|
|
197
|
-
if len(slug) > 255:
|
|
198
|
-
raise ValueError("Tenant slug too long (max 255 characters)")
|
|
199
|
-
|
|
200
|
-
import re
|
|
201
|
-
|
|
202
|
-
if not re.match(r"^[a-z0-9-]+$", slug):
|
|
203
|
-
raise ValueError("Tenant slug must contain only lowercase letters, numbers, and hyphens")
|
|
204
|
-
|
|
205
|
-
if slug.startswith("-") or slug.endswith("-"):
|
|
206
|
-
raise ValueError("Tenant slug cannot start or end with hyphen")
|
|
207
|
-
|
|
208
|
-
return slug
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def _extract_tenant_from_email_domain(email: str) -> str | None:
|
|
212
|
-
"""
|
|
213
|
-
Extract tenant slug from email domain.
|
|
214
|
-
|
|
215
|
-
This is useful for organizations that want to automatically
|
|
216
|
-
assign tenants based on email domains.
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
email: Email address
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
Tenant slug derived from domain, or None if not applicable
|
|
223
|
-
"""
|
|
224
|
-
try:
|
|
225
|
-
domain = email.split("@")[1].lower()
|
|
226
|
-
|
|
227
|
-
# Skip common public email domains
|
|
228
|
-
public_domains = {
|
|
229
|
-
"gmail.com",
|
|
230
|
-
"yahoo.com",
|
|
231
|
-
"outlook.com",
|
|
232
|
-
"hotmail.com",
|
|
233
|
-
"icloud.com",
|
|
234
|
-
"protonmail.com",
|
|
235
|
-
"aol.com",
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if domain in public_domains:
|
|
239
|
-
logger.debug(
|
|
240
|
-
"Skipping tenant extraction from public email domain",
|
|
241
|
-
domain=domain,
|
|
242
|
-
)
|
|
243
|
-
return None
|
|
244
|
-
|
|
245
|
-
# Extract organization name from domain
|
|
246
|
-
# e.g., "user@acme-corp.com" -> "acme-corp"
|
|
247
|
-
org_name = domain.split(".")[0]
|
|
248
|
-
return _normalize_tenant_slug(org_name)
|
|
249
|
-
|
|
250
|
-
except (IndexError, AttributeError):
|
|
251
|
-
logger.warning("Invalid email format for domain extraction", email=email)
|
|
252
|
-
return None
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
def get_tenant_extraction_config() -> dict[str, Any]:
|
|
256
|
-
"""
|
|
257
|
-
Get current tenant extraction configuration.
|
|
258
|
-
|
|
259
|
-
Returns:
|
|
260
|
-
Dictionary with tenant extraction settings
|
|
261
|
-
"""
|
|
262
|
-
return {
|
|
263
|
-
"multi_tenant_mode": settings.multi_tenant_mode,
|
|
264
|
-
"default_tenant_slug": settings.default_tenant_slug,
|
|
265
|
-
"custom_tenant_claim": getattr(settings, "jwt_tenant_claim", None),
|
|
266
|
-
"domain_based_extraction": settings.multi_tenant_mode,
|
|
267
|
-
"supported_claim_names": [
|
|
268
|
-
"tenant",
|
|
269
|
-
"org",
|
|
270
|
-
"organization",
|
|
271
|
-
"org_slug",
|
|
272
|
-
"org_name",
|
|
273
|
-
"namespace",
|
|
274
|
-
"group",
|
|
275
|
-
"team",
|
|
276
|
-
"workspace",
|
|
277
|
-
],
|
|
278
|
-
}
|