@weirdfingers/baseboards 0.9.5 → 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 +561 -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,220 +0,0 @@
|
|
|
1
|
-
"""Auth0 OIDC authentication adapter."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
import httpx
|
|
9
|
-
import jwt
|
|
10
|
-
|
|
11
|
-
from ...logging import get_logger
|
|
12
|
-
from .base import AuthenticationError, Principal
|
|
13
|
-
|
|
14
|
-
logger = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class Auth0OIDCAdapter:
|
|
18
|
-
"""Auth0 OIDC authentication adapter."""
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
domain: str,
|
|
23
|
-
audience: str,
|
|
24
|
-
client_id: str | None = None,
|
|
25
|
-
client_secret: str | None = None,
|
|
26
|
-
):
|
|
27
|
-
"""
|
|
28
|
-
Initialize Auth0 adapter.
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
domain: Auth0 domain (e.g., "myapp.us.auth0.com")
|
|
32
|
-
audience: Auth0 API identifier/audience
|
|
33
|
-
client_id: Optional client ID for API calls
|
|
34
|
-
client_secret: Optional client secret for API calls
|
|
35
|
-
"""
|
|
36
|
-
self.domain = domain
|
|
37
|
-
self.audience = audience
|
|
38
|
-
self.client_id = client_id
|
|
39
|
-
self.client_secret = client_secret
|
|
40
|
-
self.issuer = f"https://{domain}/"
|
|
41
|
-
self.jwks_url = f"https://{domain}/.well-known/jwks.json"
|
|
42
|
-
self._jwks_cache: dict[str, Any] = {}
|
|
43
|
-
self._http_client = httpx.AsyncClient()
|
|
44
|
-
|
|
45
|
-
async def verify_token(self, token: str) -> Principal:
|
|
46
|
-
"""Verify an Auth0 JWT token and return the principal."""
|
|
47
|
-
try:
|
|
48
|
-
# JWT library already imported
|
|
49
|
-
from jwt.exceptions import InvalidTokenError
|
|
50
|
-
|
|
51
|
-
# Get JWKS for verification
|
|
52
|
-
jwks = await self._get_jwks()
|
|
53
|
-
|
|
54
|
-
# Decode JWT header to get key ID
|
|
55
|
-
unverified_header = jwt.get_unverified_header(token)
|
|
56
|
-
kid = unverified_header.get("kid")
|
|
57
|
-
|
|
58
|
-
if not kid:
|
|
59
|
-
raise AuthenticationError("Missing 'kid' in JWT header")
|
|
60
|
-
|
|
61
|
-
# Find the matching key
|
|
62
|
-
key = None
|
|
63
|
-
for jwk in jwks.get("keys", []):
|
|
64
|
-
if jwk.get("kid") == kid:
|
|
65
|
-
# Store the JWK - PyJWT handles conversion internally
|
|
66
|
-
key = jwk
|
|
67
|
-
break
|
|
68
|
-
|
|
69
|
-
if not key:
|
|
70
|
-
raise AuthenticationError(f"Unable to find key with kid: {kid}")
|
|
71
|
-
|
|
72
|
-
# Verify and decode the token
|
|
73
|
-
payload = jwt.decode(
|
|
74
|
-
token,
|
|
75
|
-
key,
|
|
76
|
-
algorithms=["RS256"],
|
|
77
|
-
issuer=self.issuer,
|
|
78
|
-
audience=self.audience,
|
|
79
|
-
options={
|
|
80
|
-
"verify_exp": True,
|
|
81
|
-
"verify_nbf": True,
|
|
82
|
-
"verify_iat": True,
|
|
83
|
-
"verify_aud": True,
|
|
84
|
-
"verify_iss": True,
|
|
85
|
-
},
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
# Extract required claims
|
|
89
|
-
subject = payload.get("sub")
|
|
90
|
-
if not subject:
|
|
91
|
-
raise AuthenticationError("Missing 'sub' claim in token")
|
|
92
|
-
|
|
93
|
-
# Build principal from Auth0 claims
|
|
94
|
-
principal = Principal(
|
|
95
|
-
provider="auth0",
|
|
96
|
-
subject=subject,
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
# Add optional claims
|
|
100
|
-
if email := payload.get("email"):
|
|
101
|
-
principal["email"] = email
|
|
102
|
-
|
|
103
|
-
# Extract name information
|
|
104
|
-
if name := payload.get("name"):
|
|
105
|
-
principal["display_name"] = name
|
|
106
|
-
elif given_name := payload.get("given_name"):
|
|
107
|
-
family_name = payload.get("family_name", "")
|
|
108
|
-
principal["display_name"] = f"{given_name} {family_name}".strip()
|
|
109
|
-
elif nickname := payload.get("nickname"):
|
|
110
|
-
principal["display_name"] = nickname
|
|
111
|
-
|
|
112
|
-
if picture := payload.get("picture"):
|
|
113
|
-
principal["avatar_url"] = picture
|
|
114
|
-
|
|
115
|
-
# Store all claims for additional context
|
|
116
|
-
principal["claims"] = payload
|
|
117
|
-
|
|
118
|
-
return principal
|
|
119
|
-
|
|
120
|
-
except ImportError as e:
|
|
121
|
-
raise AuthenticationError("PyJWT is required for Auth0 authentication") from e
|
|
122
|
-
except InvalidTokenError as e:
|
|
123
|
-
logger.warning(f"Auth0 JWT token validation failed: {e}")
|
|
124
|
-
raise AuthenticationError(f"Invalid token: {e}") from e
|
|
125
|
-
except Exception as e:
|
|
126
|
-
logger.error(f"Unexpected error verifying Auth0 token: {e}")
|
|
127
|
-
raise AuthenticationError("Token verification failed") from e
|
|
128
|
-
|
|
129
|
-
async def issue_token(self, user_id: UUID | None = None, claims: dict | None = None) -> str:
|
|
130
|
-
"""
|
|
131
|
-
Issue a new token via Auth0 Management API (requires client credentials).
|
|
132
|
-
|
|
133
|
-
This is rarely used as Auth0 typically handles token issuance via client libraries.
|
|
134
|
-
"""
|
|
135
|
-
if not self.client_id or not self.client_secret:
|
|
136
|
-
raise NotImplementedError("Token issuance requires client_id and client_secret")
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
# Get management API access token first
|
|
140
|
-
await self._get_management_token()
|
|
141
|
-
|
|
142
|
-
# This would require implementing Auth0's Management API token creation
|
|
143
|
-
# which is complex and rarely used. Most apps use client-side auth.
|
|
144
|
-
raise NotImplementedError(
|
|
145
|
-
"Server-side token issuance not commonly supported. "
|
|
146
|
-
"Use Auth0 client libraries for authentication."
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
except Exception as e:
|
|
150
|
-
logger.error(f"Failed to issue Auth0 token: {e}")
|
|
151
|
-
raise AuthenticationError("Token issuance failed") from e
|
|
152
|
-
|
|
153
|
-
async def get_user_info(self, token: str) -> dict:
|
|
154
|
-
"""Get additional user information from Auth0 userinfo endpoint."""
|
|
155
|
-
try:
|
|
156
|
-
response = await self._http_client.get(
|
|
157
|
-
f"https://{self.domain}/userinfo",
|
|
158
|
-
headers={
|
|
159
|
-
"Authorization": f"Bearer {token}",
|
|
160
|
-
"Content-Type": "application/json",
|
|
161
|
-
},
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if response.status_code == 200:
|
|
165
|
-
return response.json()
|
|
166
|
-
else:
|
|
167
|
-
logger.warning(f"Failed to get Auth0 user info: {response.status_code}")
|
|
168
|
-
return {}
|
|
169
|
-
|
|
170
|
-
except Exception as e:
|
|
171
|
-
logger.warning(f"Failed to get Auth0 user info: {e}")
|
|
172
|
-
return {}
|
|
173
|
-
|
|
174
|
-
async def _get_jwks(self) -> dict[str, Any]:
|
|
175
|
-
"""Get JWKS from Auth0 for JWT verification."""
|
|
176
|
-
try:
|
|
177
|
-
# Check cache first (in production, implement TTL)
|
|
178
|
-
if self._jwks_cache:
|
|
179
|
-
return self._jwks_cache
|
|
180
|
-
|
|
181
|
-
response = await self._http_client.get(self.jwks_url)
|
|
182
|
-
response.raise_for_status()
|
|
183
|
-
|
|
184
|
-
jwks = response.json()
|
|
185
|
-
self._jwks_cache = jwks
|
|
186
|
-
|
|
187
|
-
return jwks
|
|
188
|
-
|
|
189
|
-
except Exception as e:
|
|
190
|
-
logger.error(f"Failed to fetch JWKS from Auth0: {e}")
|
|
191
|
-
raise AuthenticationError("Unable to verify token - JWKS unavailable") from e
|
|
192
|
-
|
|
193
|
-
async def _get_management_token(self) -> str:
|
|
194
|
-
"""Get Auth0 Management API access token."""
|
|
195
|
-
try:
|
|
196
|
-
response = await self._http_client.post(
|
|
197
|
-
f"https://{self.domain}/oauth/token",
|
|
198
|
-
json={
|
|
199
|
-
"client_id": self.client_id,
|
|
200
|
-
"client_secret": self.client_secret,
|
|
201
|
-
"audience": f"https://{self.domain}/api/v2/",
|
|
202
|
-
"grant_type": "client_credentials",
|
|
203
|
-
},
|
|
204
|
-
headers={"Content-Type": "application/json"},
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
response.raise_for_status()
|
|
208
|
-
data = response.json()
|
|
209
|
-
|
|
210
|
-
return data["access_token"]
|
|
211
|
-
|
|
212
|
-
except Exception as e:
|
|
213
|
-
logger.error(f"Failed to get Auth0 management token: {e}")
|
|
214
|
-
raise AuthenticationError("Unable to get management token") from e
|
|
215
|
-
|
|
216
|
-
async def __aenter__(self):
|
|
217
|
-
return self
|
|
218
|
-
|
|
219
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
220
|
-
await self._http_client.aclose()
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"""Base authentication adapter interface and types."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Literal, NotRequired, Protocol, TypedDict
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Principal(TypedDict):
|
|
10
|
-
"""Identity extracted from an incoming token."""
|
|
11
|
-
|
|
12
|
-
provider: Literal["supabase", "clerk", "auth0", "oidc", "jwt", "none"]
|
|
13
|
-
subject: str # provider user id (sub)
|
|
14
|
-
email: NotRequired[str]
|
|
15
|
-
display_name: NotRequired[str]
|
|
16
|
-
avatar_url: NotRequired[str]
|
|
17
|
-
claims: NotRequired[dict]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class AuthAdapter(Protocol):
|
|
21
|
-
"""Provider-agnostic authentication adapter interface."""
|
|
22
|
-
|
|
23
|
-
async def verify_token(self, token: str) -> Principal:
|
|
24
|
-
"""
|
|
25
|
-
Verify a token and return the principal identity.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
token: The authentication token to verify
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
Principal containing identity information
|
|
32
|
-
|
|
33
|
-
Raises:
|
|
34
|
-
AuthenticationError: If token is invalid or expired
|
|
35
|
-
"""
|
|
36
|
-
...
|
|
37
|
-
|
|
38
|
-
async def issue_token(self, user_id: UUID | None = None, claims: dict | None = None) -> str:
|
|
39
|
-
"""
|
|
40
|
-
Issue a new token (optional - not all providers support this).
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
user_id: Optional user ID to include in token
|
|
44
|
-
claims: Optional additional claims to include
|
|
45
|
-
|
|
46
|
-
Returns:
|
|
47
|
-
Signed token string
|
|
48
|
-
"""
|
|
49
|
-
...
|
|
50
|
-
|
|
51
|
-
async def get_user_info(self, token: str) -> dict:
|
|
52
|
-
"""
|
|
53
|
-
Get provider-specific user information (optional enrichment).
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
token: Valid authentication token
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
Dictionary of provider-specific user data
|
|
60
|
-
"""
|
|
61
|
-
...
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class AuthenticationError(Exception):
|
|
65
|
-
"""Raised when authentication fails."""
|
|
66
|
-
|
|
67
|
-
pass
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class AuthorizationError(Exception):
|
|
71
|
-
"""Raised when authorization fails."""
|
|
72
|
-
|
|
73
|
-
pass
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
"""Clerk authentication adapter."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
import httpx
|
|
9
|
-
|
|
10
|
-
from ...logging import get_logger
|
|
11
|
-
from .base import AuthenticationError, Principal
|
|
12
|
-
|
|
13
|
-
logger = get_logger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ClerkAuthAdapter:
|
|
17
|
-
"""Clerk authentication adapter."""
|
|
18
|
-
|
|
19
|
-
def __init__(self, secret_key: str, jwks_url: str | None = None):
|
|
20
|
-
"""
|
|
21
|
-
Initialize Clerk adapter.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
secret_key: Clerk secret key for API calls
|
|
25
|
-
jwks_url: Optional JWKS URL for JWT verification (auto-discovered if not provided)
|
|
26
|
-
"""
|
|
27
|
-
self.secret_key = secret_key
|
|
28
|
-
self.jwks_url = jwks_url or "https://api.clerk.dev/v1/jwks"
|
|
29
|
-
self._jwks_cache: dict[str, Any] = {}
|
|
30
|
-
self._http_client = httpx.AsyncClient()
|
|
31
|
-
|
|
32
|
-
async def verify_token(self, token: str) -> Principal:
|
|
33
|
-
"""Verify a Clerk JWT token and return the principal."""
|
|
34
|
-
try:
|
|
35
|
-
import jwt
|
|
36
|
-
from jwt.exceptions import InvalidTokenError
|
|
37
|
-
|
|
38
|
-
# Get JWKS for verification
|
|
39
|
-
jwks = await self._get_jwks()
|
|
40
|
-
|
|
41
|
-
# Decode JWT header to get key ID
|
|
42
|
-
unverified_header = jwt.get_unverified_header(token)
|
|
43
|
-
kid = unverified_header.get("kid")
|
|
44
|
-
|
|
45
|
-
if not kid:
|
|
46
|
-
raise AuthenticationError("Missing 'kid' in JWT header")
|
|
47
|
-
|
|
48
|
-
# Find the matching key
|
|
49
|
-
key = None
|
|
50
|
-
for jwk in jwks.get("keys", []):
|
|
51
|
-
if jwk.get("kid") == kid:
|
|
52
|
-
# Store the JWK - PyJWT handles conversion internally
|
|
53
|
-
key = jwk
|
|
54
|
-
break
|
|
55
|
-
|
|
56
|
-
if not key:
|
|
57
|
-
raise AuthenticationError(f"Unable to find key with kid: {kid}")
|
|
58
|
-
|
|
59
|
-
# Verify and decode the token
|
|
60
|
-
payload = jwt.decode(
|
|
61
|
-
token,
|
|
62
|
-
key,
|
|
63
|
-
algorithms=["RS256"],
|
|
64
|
-
options={
|
|
65
|
-
"verify_exp": True,
|
|
66
|
-
"verify_nbf": True,
|
|
67
|
-
"verify_iat": True,
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# Extract required claims
|
|
72
|
-
subject = payload.get("sub")
|
|
73
|
-
if not subject:
|
|
74
|
-
raise AuthenticationError("Missing 'sub' claim in token")
|
|
75
|
-
|
|
76
|
-
# Build principal from Clerk claims
|
|
77
|
-
principal = Principal(
|
|
78
|
-
provider="clerk",
|
|
79
|
-
subject=subject,
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
# Add optional claims
|
|
83
|
-
if email := payload.get("email"):
|
|
84
|
-
principal["email"] = email
|
|
85
|
-
elif email_addresses := payload.get("email_addresses"):
|
|
86
|
-
# Clerk sometimes uses email_addresses array
|
|
87
|
-
if email_addresses and len(email_addresses) > 0:
|
|
88
|
-
principal["email"] = email_addresses[0].get("email_address")
|
|
89
|
-
|
|
90
|
-
# Extract name information
|
|
91
|
-
if first_name := payload.get("given_name"):
|
|
92
|
-
last_name = payload.get("family_name", "")
|
|
93
|
-
principal["display_name"] = f"{first_name} {last_name}".strip()
|
|
94
|
-
elif name := payload.get("name"):
|
|
95
|
-
principal["display_name"] = name
|
|
96
|
-
|
|
97
|
-
if picture := payload.get("picture"):
|
|
98
|
-
principal["avatar_url"] = picture
|
|
99
|
-
|
|
100
|
-
# Store all claims for additional context
|
|
101
|
-
principal["claims"] = payload
|
|
102
|
-
|
|
103
|
-
return principal
|
|
104
|
-
|
|
105
|
-
except ImportError as e:
|
|
106
|
-
raise AuthenticationError("PyJWT is required for Clerk authentication") from e
|
|
107
|
-
except InvalidTokenError as e:
|
|
108
|
-
logger.warning(f"Clerk JWT token validation failed: {e}")
|
|
109
|
-
raise AuthenticationError(f"Invalid token: {e}") from e
|
|
110
|
-
except Exception as e:
|
|
111
|
-
logger.error(f"Unexpected error verifying Clerk token: {e}")
|
|
112
|
-
raise AuthenticationError("Token verification failed") from e
|
|
113
|
-
|
|
114
|
-
async def issue_token(self, user_id: UUID | None = None, claims: dict | None = None) -> str:
|
|
115
|
-
"""
|
|
116
|
-
Issue a new token via Clerk (not commonly used).
|
|
117
|
-
|
|
118
|
-
Note: Clerk typically handles token issuance on the client side.
|
|
119
|
-
This method is provided for completeness but may not be used in practice.
|
|
120
|
-
"""
|
|
121
|
-
raise NotImplementedError("Token issuance should be handled by Clerk client libraries")
|
|
122
|
-
|
|
123
|
-
async def get_user_info(self, token: str) -> dict:
|
|
124
|
-
"""Get additional user information from Clerk API."""
|
|
125
|
-
try:
|
|
126
|
-
# First verify the token to get the subject
|
|
127
|
-
principal = await self.verify_token(token)
|
|
128
|
-
user_id = principal["subject"]
|
|
129
|
-
|
|
130
|
-
# Get additional user info from Clerk API
|
|
131
|
-
response = await self._http_client.get(
|
|
132
|
-
f"https://api.clerk.dev/v1/users/{user_id}",
|
|
133
|
-
headers={
|
|
134
|
-
"Authorization": f"Bearer {self.secret_key}",
|
|
135
|
-
"Content-Type": "application/json",
|
|
136
|
-
},
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
if response.status_code == 200:
|
|
140
|
-
return response.json()
|
|
141
|
-
else:
|
|
142
|
-
logger.warning(f"Failed to get Clerk user info: {response.status_code}")
|
|
143
|
-
return {}
|
|
144
|
-
|
|
145
|
-
except Exception as e:
|
|
146
|
-
logger.warning(f"Failed to get Clerk user info: {e}")
|
|
147
|
-
return {}
|
|
148
|
-
|
|
149
|
-
async def _get_jwks(self) -> dict[str, Any]:
|
|
150
|
-
"""Get JWKS from Clerk for JWT verification."""
|
|
151
|
-
try:
|
|
152
|
-
# Check cache first
|
|
153
|
-
if self._jwks_cache:
|
|
154
|
-
return self._jwks_cache
|
|
155
|
-
|
|
156
|
-
response = await self._http_client.get(self.jwks_url)
|
|
157
|
-
response.raise_for_status()
|
|
158
|
-
|
|
159
|
-
jwks = response.json()
|
|
160
|
-
self._jwks_cache = jwks
|
|
161
|
-
|
|
162
|
-
return jwks
|
|
163
|
-
|
|
164
|
-
except Exception as e:
|
|
165
|
-
logger.error(f"Failed to fetch JWKS from Clerk: {e}")
|
|
166
|
-
raise AuthenticationError("Unable to verify token - JWKS unavailable") from e
|
|
167
|
-
|
|
168
|
-
async def __aenter__(self):
|
|
169
|
-
return self
|
|
170
|
-
|
|
171
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
172
|
-
await self._http_client.aclose()
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"""JWT authentication adapter for self-issued tokens."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from datetime import UTC, datetime, timedelta
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
import jwt
|
|
9
|
-
from jwt.exceptions import InvalidTokenError
|
|
10
|
-
|
|
11
|
-
from ...logging import get_logger
|
|
12
|
-
from .base import AuthenticationError, Principal
|
|
13
|
-
|
|
14
|
-
logger = get_logger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class JWTAuthAdapter:
|
|
18
|
-
"""JWT authentication adapter for self-issued tokens."""
|
|
19
|
-
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
secret_key: str,
|
|
23
|
-
algorithm: str = "HS256",
|
|
24
|
-
issuer: str = "boards",
|
|
25
|
-
audience: str = "boards-api",
|
|
26
|
-
token_expiry_hours: int = 24,
|
|
27
|
-
):
|
|
28
|
-
self.secret_key = secret_key
|
|
29
|
-
self.algorithm = algorithm
|
|
30
|
-
self.issuer = issuer
|
|
31
|
-
self.audience = audience
|
|
32
|
-
self.token_expiry_hours = token_expiry_hours
|
|
33
|
-
|
|
34
|
-
async def verify_token(self, token: str) -> Principal:
|
|
35
|
-
"""Verify a JWT token and return the principal."""
|
|
36
|
-
try:
|
|
37
|
-
payload = jwt.decode(
|
|
38
|
-
token,
|
|
39
|
-
self.secret_key,
|
|
40
|
-
algorithms=[self.algorithm],
|
|
41
|
-
issuer=self.issuer,
|
|
42
|
-
audience=self.audience,
|
|
43
|
-
options={
|
|
44
|
-
"verify_exp": True,
|
|
45
|
-
"verify_nbf": True,
|
|
46
|
-
"verify_iat": True,
|
|
47
|
-
},
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
# Extract required claims
|
|
51
|
-
subject = payload.get("sub")
|
|
52
|
-
if not subject:
|
|
53
|
-
raise AuthenticationError("Missing 'sub' claim in token")
|
|
54
|
-
|
|
55
|
-
# Build principal from JWT claims
|
|
56
|
-
principal = Principal(
|
|
57
|
-
provider="jwt",
|
|
58
|
-
subject=subject,
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# Add optional claims
|
|
62
|
-
if email := payload.get("email"):
|
|
63
|
-
principal["email"] = email
|
|
64
|
-
if display_name := payload.get("name"):
|
|
65
|
-
principal["display_name"] = display_name
|
|
66
|
-
if avatar_url := payload.get("picture"):
|
|
67
|
-
principal["avatar_url"] = avatar_url
|
|
68
|
-
|
|
69
|
-
# Store all claims for additional context
|
|
70
|
-
principal["claims"] = payload
|
|
71
|
-
|
|
72
|
-
return principal
|
|
73
|
-
|
|
74
|
-
except AuthenticationError:
|
|
75
|
-
# Re-raise our own authentication errors
|
|
76
|
-
raise
|
|
77
|
-
except InvalidTokenError as e:
|
|
78
|
-
logger.warning("JWT token validation failed", error=str(e))
|
|
79
|
-
raise AuthenticationError("Invalid token") from e
|
|
80
|
-
except Exception as e:
|
|
81
|
-
logger.error(f"Unexpected error verifying JWT token: {e}")
|
|
82
|
-
raise AuthenticationError("Token verification failed") from e
|
|
83
|
-
|
|
84
|
-
async def issue_token(self, user_id: UUID | None = None, claims: dict | None = None) -> str:
|
|
85
|
-
"""Issue a new JWT token."""
|
|
86
|
-
now = datetime.now(UTC)
|
|
87
|
-
|
|
88
|
-
payload = {
|
|
89
|
-
"iss": self.issuer,
|
|
90
|
-
"aud": self.audience,
|
|
91
|
-
"iat": now,
|
|
92
|
-
"nbf": now,
|
|
93
|
-
"exp": now + timedelta(hours=self.token_expiry_hours),
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if user_id:
|
|
97
|
-
payload["sub"] = str(user_id)
|
|
98
|
-
|
|
99
|
-
if claims:
|
|
100
|
-
payload.update(claims)
|
|
101
|
-
|
|
102
|
-
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
|
103
|
-
|
|
104
|
-
async def get_user_info(self, token: str) -> dict:
|
|
105
|
-
"""Get user info from JWT claims."""
|
|
106
|
-
try:
|
|
107
|
-
payload = jwt.decode(
|
|
108
|
-
token,
|
|
109
|
-
self.secret_key,
|
|
110
|
-
algorithms=[self.algorithm],
|
|
111
|
-
issuer=self.issuer,
|
|
112
|
-
audience=self.audience,
|
|
113
|
-
options={
|
|
114
|
-
"verify_exp": True,
|
|
115
|
-
"verify_nbf": True,
|
|
116
|
-
"verify_iat": True,
|
|
117
|
-
},
|
|
118
|
-
)
|
|
119
|
-
return payload
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger.warning("Failed to decode JWT for user info", error=str(e))
|
|
122
|
-
return {}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
"""No-auth adapter for local development without authentication."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import os
|
|
6
|
-
from uuid import UUID
|
|
7
|
-
|
|
8
|
-
from ...logging import get_logger
|
|
9
|
-
from .base import AuthenticationError, Principal
|
|
10
|
-
|
|
11
|
-
logger = get_logger(__name__)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class NoAuthAdapter:
|
|
15
|
-
"""
|
|
16
|
-
No-auth adapter that bypasses authentication for local development.
|
|
17
|
-
|
|
18
|
-
This adapter treats any request as authenticated with a default test user.
|
|
19
|
-
WARNING: Only use this in development environments!
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
def __init__(self, default_user_id: str = "dev-user", default_tenant: str = "default"):
|
|
23
|
-
self.default_user_id = default_user_id
|
|
24
|
-
self.default_tenant = default_tenant
|
|
25
|
-
|
|
26
|
-
# Production safety checks
|
|
27
|
-
environment = os.getenv("ENVIRONMENT", "").lower()
|
|
28
|
-
if environment in ("production", "prod"):
|
|
29
|
-
logger.error(
|
|
30
|
-
"NoAuthAdapter detected in production environment! "
|
|
31
|
-
"This is a security risk and should never be used in production.",
|
|
32
|
-
environment=environment,
|
|
33
|
-
)
|
|
34
|
-
raise RuntimeError(
|
|
35
|
-
"NoAuthAdapter cannot be used in production environments. "
|
|
36
|
-
"Please configure a proper authentication provider."
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
# Check for production-like domains
|
|
40
|
-
api_url = os.getenv("BOARDS_API_URL", "")
|
|
41
|
-
if api_url and not any(
|
|
42
|
-
domain in api_url for domain in ["localhost", "127.0.0.1", "dev.", "staging."]
|
|
43
|
-
):
|
|
44
|
-
logger.warning(
|
|
45
|
-
"NoAuthAdapter detected with production-like URL. "
|
|
46
|
-
"Ensure this is intentional and not deployed to production.",
|
|
47
|
-
api_url=api_url,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
logger.warning(
|
|
51
|
-
"NoAuthAdapter is active - ALL requests will be treated as authenticated! "
|
|
52
|
-
"This should ONLY be used in development.",
|
|
53
|
-
user_id=default_user_id,
|
|
54
|
-
environment=os.getenv("ENVIRONMENT", "unknown"),
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
async def verify_token(self, token: str) -> Principal:
|
|
58
|
-
"""
|
|
59
|
-
Always returns a default principal - no actual verification.
|
|
60
|
-
|
|
61
|
-
Any non-empty token will be accepted. The token content doesn't matter.
|
|
62
|
-
"""
|
|
63
|
-
if not token:
|
|
64
|
-
raise AuthenticationError("Token required (even in no-auth mode)")
|
|
65
|
-
|
|
66
|
-
# Return a default principal for development
|
|
67
|
-
principal = Principal(
|
|
68
|
-
provider="none",
|
|
69
|
-
subject=self.default_user_id,
|
|
70
|
-
email="dev@example.com",
|
|
71
|
-
display_name="Development User",
|
|
72
|
-
claims={
|
|
73
|
-
"mode": "development",
|
|
74
|
-
"token": token[:20] + "..." if len(token) > 20 else token,
|
|
75
|
-
},
|
|
76
|
-
)
|
|
77
|
-
# avatar_url is NotRequired, so we can add it as None if needed for tests
|
|
78
|
-
principal["avatar_url"] = ""
|
|
79
|
-
return principal
|
|
80
|
-
|
|
81
|
-
async def issue_token(self, user_id: UUID | None = None, claims: dict | None = None) -> str:
|
|
82
|
-
"""Issue a fake development token."""
|
|
83
|
-
token_parts = [
|
|
84
|
-
"dev-token",
|
|
85
|
-
str(user_id) if user_id else self.default_user_id,
|
|
86
|
-
"no-auth-mode",
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
if claims:
|
|
90
|
-
token_parts.extend(f"{k}={v}" for k, v in claims.items())
|
|
91
|
-
|
|
92
|
-
return "|".join(token_parts)
|
|
93
|
-
|
|
94
|
-
async def get_user_info(self, token: str) -> dict:
|
|
95
|
-
"""Return fake user info for development."""
|
|
96
|
-
return {
|
|
97
|
-
"id": self.default_user_id,
|
|
98
|
-
"email": "dev@example.com",
|
|
99
|
-
"name": "Development User",
|
|
100
|
-
"mode": "no-auth",
|
|
101
|
-
"token_preview": token[:20] + "..." if len(token) > 20 else token,
|
|
102
|
-
}
|