@inneranimalmedia/agentsam-sdk 2.6.5 → 2.6.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/AGENTSAM.md +29 -0
- package/apps/README.md +5 -0
- package/apps/cad-creator/README.md +4 -4
- package/apps/cad-creator/shared/cad/package.json +1 -1
- package/apps/client-cms-editor/AGENTS.md +12 -7
- package/apps/client-cms-editor/README.md +76 -48
- package/apps/client-cms-editor/agentsam.app.json +26 -9
- package/apps/client-cms-editor/backend/package.json +20 -3
- package/apps/client-cms-editor/backend/src/api/client.ts +134 -101
- package/apps/client-cms-editor/backend/src/index.ts +1 -1
- package/apps/client-cms-editor/backend/src/model.ts +11 -115
- package/apps/client-cms-editor/backend/src/preview/bridge.ts +1 -1
- package/apps/client-cms-editor/backend/tsconfig.json +6 -1
- package/apps/client-cms-editor/bin/agentsam-cms.js +262 -0
- package/apps/client-cms-editor/bin/agentsam-cms.mjs +2 -228
- package/apps/client-cms-editor/frontend/package.json +16 -7
- package/apps/client-cms-editor/frontend/src/AgentSamDrawer.tsx +9 -41
- package/apps/client-cms-editor/frontend/src/CmsAgentSurface.tsx +19 -174
- package/apps/client-cms-editor/frontend/src/adapters/heuristic.ts +29 -0
- package/apps/client-cms-editor/frontend/src/adapters/http.ts +628 -0
- package/apps/client-cms-editor/frontend/src/adapters/memory.ts +510 -0
- package/apps/client-cms-editor/frontend/src/editor/CmsEditor.tsx +213 -0
- package/apps/client-cms-editor/frontend/src/editor/CmsEditorProvider.tsx +20 -0
- package/apps/client-cms-editor/frontend/src/editor/canvas/PreviewCanvas.tsx +53 -0
- package/apps/client-cms-editor/frontend/src/{CmsEditor.tsx → editor/legacy/CmsEditorPrototype.tsx} +52 -23
- package/apps/client-cms-editor/frontend/src/editor/useCmsEditorController.ts +218 -0
- package/apps/client-cms-editor/frontend/src/index.ts +19 -5
- package/apps/client-cms-editor/frontend/src/lib/agent-host.ts +24 -0
- package/apps/client-cms-editor/frontend/src/main.tsx +82 -8
- package/apps/client-cms-editor/frontend/src/mount.tsx +92 -30
- package/apps/client-cms-editor/frontend/src/styles/agentsam-drawer.css +1 -1
- package/apps/client-cms-editor/frontend/src/styles/cmsShell.css +8 -8
- package/apps/client-cms-editor/frontend/src/styles/studio.css +55 -3
- package/apps/client-cms-editor/frontend/tsconfig.json +12 -1
- package/apps/client-cms-editor/frontend/vite.config.ts +12 -0
- package/apps/client-cms-editor/package-lock.json +973 -134
- package/apps/client-cms-editor/package.json +118 -6
- package/apps/client-cms-editor/reference/harvest/HARVEST_RECEIPT.json +201 -0
- package/apps/client-cms-editor/reference/harvest/PROVENANCE_HISTORY.txt +10 -0
- package/apps/client-cms-editor/reference/harvest/README.md +8 -0
- package/apps/client-cms-editor/reference/harvest/plans/ARCHIVE-INTAKE.md +135 -0
- package/apps/client-cms-editor/reference/harvest/plans/CHATGPT-IDENTITY-HARVEST.md +83 -0
- package/apps/client-cms-editor/reference/harvest/plans/CMS-EDITOR-HARVEST.md +166 -0
- package/apps/client-cms-editor/reference/harvest/plans/HARVEST-INDEX.md +196 -0
- package/apps/client-cms-editor/reference/harvest/plans/INNERANIMALS-CONTENT-001.md +137 -0
- package/apps/client-cms-editor/reference/harvest/plans/NO-IMPLICIT-SIDE-EFFECTS.md +87 -0
- package/apps/client-cms-editor/reference/harvest/plans/STOREFRONT-SURFACE-MAP.md +140 -0
- package/apps/client-cms-editor/reference/harvest/plans/THEME-INNERANIMALS-HARVEST.md +137 -0
- package/apps/client-cms-editor/reference/harvest/plans/TOOLING-LAB.md +63 -0
- package/apps/client-cms-editor/reference/harvest/plans/harvest-manifest.json +97 -0
- package/apps/client-cms-editor/reference/harvest/plans/local-cms-harvest-schema.sql +328 -0
- package/apps/client-cms-editor/shared/cms/package.json +17 -7
- package/apps/client-cms-editor/shared/cms/src/adapter.ts +204 -0
- package/apps/client-cms-editor/shared/cms/src/agent-context.ts +7 -5
- package/apps/client-cms-editor/shared/cms/src/editor-types.ts +32 -0
- package/apps/client-cms-editor/shared/cms/src/host.ts +82 -0
- package/apps/client-cms-editor/shared/cms/src/index.ts +4 -0
- package/apps/client-cms-editor/shared/cms/src/map.ts +115 -0
- package/apps/client-cms-editor/shared/cms/src/starter-pack.ts +158 -0
- package/contracts/source-types.v1.json +353 -0
- package/docs/CMS_CONTEXTUAL_COMPOSER.md +1 -1
- package/docs/PRODUCT_LIFECYCLE.md +566 -0
- package/docs/THEME_REFINERY.md +2 -2
- package/docs/architecture/AGENTSAM_RUNTIME_PROTOCOL.md +40 -20
- package/docs/content-studio/REVISION_GATE_2026-09-27.md +1 -1
- package/docs/plans/LOCAL_STUDIO_DESKTOP_RELEASE_2026-09-29.md +150 -0
- package/migrations/d1/0017_identity_native_oauth.sql +21 -0
- package/native/agentsam-machine/Cargo.lock +426 -0
- package/native/agentsam-machine/Cargo.toml +9 -0
- package/native/agentsam-machine/cli/Cargo.toml +14 -0
- package/native/agentsam-machine/cli/src/main.rs +77 -0
- package/native/agentsam-machine/core/Cargo.toml +15 -0
- package/native/agentsam-machine/core/src/assets.rs +790 -0
- package/native/agentsam-machine/core/src/bound.rs +298 -0
- package/native/agentsam-machine/core/src/enrich.rs +706 -0
- package/native/agentsam-machine/core/src/frontend.rs +442 -0
- package/native/agentsam-machine/core/src/lib.rs +1086 -0
- package/native/agentsam-machine/core/src/liquid.rs +528 -0
- package/native/agentsam-machine/core/src/source_types.rs +78 -0
- package/package.json +51 -21
- package/packages/agentsam-brand/README.md +1 -1
- package/packages/agentsam-brand/package.json +4 -4
- package/packages/agentsam-brand/src/index.js +1 -1
- package/packages/agentsam-contracts/package.json +13 -3
- package/packages/agentsam-contracts/src/tools.ts +22 -0
- package/packages/agentsam-errors/package.json +13 -3
- package/packages/agentsam-knowledge/package.json +18 -4
- package/packages/agentsam-knowledge/src/autorag/index.js +10 -3
- package/packages/agentsam-knowledge/src/backends/index.js +33 -1
- package/packages/agentsam-knowledge/test/autorag.test.mjs +60 -1
- package/packages/agentsam-nav/package.json +24 -6
- package/packages/agentsam-queue-control/README.md +1 -1
- package/packages/agentsam-queue-control/package.json +7 -2
- package/packages/agentsam-rapid-rust/src/main.rs +1 -1
- package/packages/agentsam-repository/package.json +13 -3
- package/packages/agentsam-repository/src/source-types.js +8 -0
- package/packages/agentsam-vault/package.json +14 -4
- package/packages/agentsam-workbench/package.json +15 -4
- package/packages/agentsam-workbench/src/agent/ToolPermissionRequest.tsx +94 -0
- package/packages/agentsam-workbench/src/agent/index.ts +2 -0
- package/packages/agentsam-workbench/src/agent/tool-permission.css +134 -0
- package/packages/connectors/cloudflare/package.json +13 -3
- package/packages/connectors/cloudflare/src/api-client.js +3 -2
- package/packages/connectors/cloudflare/src/families/vectorize.js +127 -0
- package/packages/connectors/cloudflare/src/index.js +1 -0
- package/packages/heuristic-theme/README.md +2 -2
- package/packages/heuristic-theme/package.json +8 -3
- package/packages/heuristic-theme/src/registry.js +2 -2
- package/packages/heuristic-theme/src/theme.js +1 -1
- package/packages/heuristic-theme/test/theme.test.mjs +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/providers.json +27 -7
- package/packages/identity/README.md +2 -2
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +18 -15
- package/packages/identity/migrations/apply-portable.js +109 -0
- package/packages/identity/migrations/sqlite/004_identity_oauth_consent_catalog.sql +1 -1
- package/packages/identity/migrations/sqlite/005_identity_company_native.sql +52 -0
- package/packages/identity/package.json +27 -6
- package/packages/identity/schema/agentsam.identity/manifest.json +101 -0
- package/packages/identity/scripts/local-identity-bridge.mjs +13 -7
- package/packages/identity/scripts/preview-auth-portal.mjs +6 -6
- package/packages/identity/src/adapters/cloudflare-d1/index.js +8 -401
- package/packages/identity/src/adapters/iam-compat/index.js +540 -0
- package/packages/identity/src/adapters/portable-d1/index.js +18 -0
- package/packages/identity/src/adapters/portable-store.js +479 -0
- package/packages/identity/src/adapters/sqlite/index.js +13 -312
- package/packages/identity/src/contracts/auth-config.js +0 -1
- package/packages/identity/src/contracts/identity-store.js +56 -5
- package/packages/identity/src/contracts/provider.js +1 -0
- package/packages/identity/src/contracts/sign-in-options.js +104 -0
- package/packages/identity/src/core/constants.js +1 -1
- package/packages/identity/src/core/session-policy.js +31 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +6 -6
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +5 -5
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +5 -5
- package/packages/identity/src/frontend/auth-portal/shared/company-branding.js +1 -1
- package/packages/identity/src/frontend/auth-portal/shared/desktop-identity-bridge.js +6 -37
- package/packages/identity/src/index.js +28 -1
- package/packages/identity/src/migrations/README.md +23 -23
- package/packages/identity/src/oauth/README.md +1 -1
- package/packages/identity/src/oauth/credentials.js +10 -11
- package/packages/identity/src/oauth/google-desktop-exchange.js +195 -27
- package/packages/identity/src/oauth/iam-platform.js +21 -9
- package/packages/identity/src/oauth/native-handoff.js +136 -0
- package/packages/identity/src/providers/chatgpt/headers.js +65 -0
- package/packages/identity/src/providers/chatgpt/index.js +86 -0
- package/packages/identity/src/providers/chatgpt/mapper.js +43 -0
- package/packages/identity/src/providers/chatgpt/paths.js +45 -0
- package/packages/identity/src/providers/github/oauth.js +1 -2
- package/packages/identity/src/providers/github/profile.js +16 -2
- package/packages/identity/src/providers/index.js +11 -3
- package/packages/identity/src/server/identity-service.js +2 -1
- package/packages/identity/src/server/worker-router.js +71 -18
- package/packages/identity/tests/chatgpt-provider.test.mjs +75 -0
- package/packages/identity/tests/google-desktop-identity-login.test.mjs +210 -0
- package/packages/identity/tests/identity-init-scaffold.test.mjs +15 -4
- package/packages/identity/tests/lane1-identity-store-parity.test.mjs +527 -0
- package/packages/identity/tests/lane1-packed-consumer.test.mjs +175 -0
- package/packages/identity/tests/native-oauth-handoff.test.mjs +292 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +4 -4
- package/packages/identity/tests/preview-auth-portal.test.mjs +4 -2
- package/packages/identity/tests/session-routes-policy.test.mjs +13 -1
- package/packages/identity/tests/worker-runtime-boundary.test.mjs +11 -0
- package/packages/providers/completeful/package.json +7 -2
- package/packages/theme-church-site/package.json +17 -6
- package/packages/theme-church-site/src/harvest.json +4 -2
- package/packages/theme-church-site/src/index.js +7 -3
- package/packages/theme-church-site/test/theme.test.mjs +4 -2
- package/packages/theme-companions-site/package.json +17 -6
- package/packages/theme-companions-site/src/harvest.json +4 -2
- package/packages/theme-companions-site/src/index.js +7 -3
- package/packages/theme-companions-site/test/theme.test.mjs +4 -2
- package/packages/theme-floors-site/package.json +17 -6
- package/packages/theme-floors-site/src/harvest.json +4 -2
- package/packages/theme-floors-site/src/index.js +7 -3
- package/packages/theme-floors-site/test/theme.test.mjs +4 -2
- package/packages/theme-fuelnfree-site/package.json +17 -6
- package/packages/theme-fuelnfree-site/src/harvest.json +4 -2
- package/packages/theme-fuelnfree-site/src/index.js +7 -3
- package/packages/theme-fuelnfree-site/test/theme.test.mjs +4 -2
- package/packages/theme-handyman-site/package.json +17 -6
- package/packages/theme-handyman-site/src/harvest.json +4 -2
- package/packages/theme-handyman-site/src/index.js +7 -3
- package/packages/theme-handyman-site/test/theme.test.mjs +4 -2
- package/packages/theme-insurance-site/package.json +17 -6
- package/packages/theme-insurance-site/src/harvest.json +4 -2
- package/packages/theme-insurance-site/src/index.js +7 -3
- package/packages/theme-insurance-site/test/theme.test.mjs +4 -2
- package/packages/theme-scenes/README.md +1 -1
- package/packages/theme-scenes/package.json +4 -3
- package/packages/theme-scenes/src/registry.js +187 -0
- package/packages/theme-shinshu-site/package.json +17 -6
- package/packages/theme-shinshu-site/src/harvest.json +4 -2
- package/packages/theme-shinshu-site/src/index.js +7 -3
- package/packages/theme-shinshu-site/test/theme.test.mjs +4 -2
- package/packages/work-graph/package.json +14 -4
- package/protocol/FILEMETA_V1.md +2 -2
- package/protocol/capabilities/manifest.json +1 -1
- package/protocol/database/agentsam.schema-pack.v1.schema.json +72 -0
- package/protocol/presets/catalog.json +2 -2
- package/protocol/theme-refinery/lineage-registry.json +105 -0
- package/protocol/theme-refinery/product-metadata.schema.json +21 -1
- package/skills/grok-auth/SKILL.md +110 -0
- package/skills/grok-auth/agentsam.skill.json +25 -0
- package/skills/grok-auth/references/grok-identity.md +64 -0
- package/skills/grok-auth/references/per-user-data.md +49 -0
- package/skills/grok-auth/references/prewired-and-env.md +54 -0
- package/skills/grok-auth/references/session-ui.md +133 -0
- package/skills/grok-auth/references/sign-in-methods.md +55 -0
- package/skills/grok-auth/references/wiring.md +71 -0
- package/skills/grok-building-games/SKILL.md +213 -0
- package/skills/grok-building-games/agentsam.skill.json +25 -0
- package/skills/grok-building-games/references/3d-libs.md +64 -0
- package/skills/grok-building-games/references/ai-pathfinding.md +122 -0
- package/skills/grok-building-games/references/audio.md +129 -0
- package/skills/grok-building-games/references/babylon.md +202 -0
- package/skills/grok-building-games/references/collision-physics.md +122 -0
- package/skills/grok-building-games/references/ecs-architecture.md +111 -0
- package/skills/grok-building-games/references/game-feel-juice.md +110 -0
- package/skills/grok-building-games/references/genres/board-card-chess.md +89 -0
- package/skills/grok-building-games/references/genres/endless-runner.md +83 -0
- package/skills/grok-building-games/references/genres/fps.md +105 -0
- package/skills/grok-building-games/references/genres/platformer-2d.md +97 -0
- package/skills/grok-building-games/references/genres/puzzle-match3-tetris.md +131 -0
- package/skills/grok-building-games/references/genres/racing-kart.md +106 -0
- package/skills/grok-building-games/references/genres/topdown-twin-stick.md +97 -0
- package/skills/grok-building-games/references/genres/tower-defense.md +84 -0
- package/skills/grok-building-games/references/genres/voxel-minecraft.md +89 -0
- package/skills/grok-building-games/references/input.md +139 -0
- package/skills/grok-building-games/references/phaser.md +277 -0
- package/skills/grok-building-games/references/procedural-generation.md +141 -0
- package/skills/grok-building-games/references/save-persistence.md +108 -0
- package/skills/grok-building-games/references/threejs-foundational.md +148 -0
- package/skills/grok-controls/SKILL.md +274 -0
- package/skills/grok-controls/agentsam.skill.json +25 -0
- package/skills/grok-design-ui/SKILL.md +122 -0
- package/skills/grok-design-ui/agentsam.skill.json +25 -0
- package/skills/grok-design-ui/references/animations.md +379 -0
- package/skills/grok-design-ui/references/performance.md +88 -0
- package/skills/grok-design-ui/references/refined-ui.md +384 -0
- package/skills/grok-design-ui/references/surfaces.md +247 -0
- package/skills/grok-design-ui/references/typography.md +123 -0
- package/skills/grok-game-animation-frames/SKILL.md +84 -0
- package/skills/grok-game-animation-frames/agentsam.skill.json +25 -0
- package/skills/grok-game-asset-core/SKILL.md +69 -0
- package/skills/grok-game-asset-core/agentsam.skill.json +25 -0
- package/skills/grok-game-character-consistency/SKILL.md +69 -0
- package/skills/grok-game-character-consistency/agentsam.skill.json +25 -0
- package/skills/grok-game-tilesets/SKILL.md +65 -0
- package/skills/grok-game-tilesets/agentsam.skill.json +25 -0
- package/skills/grok-game-ui-icons/SKILL.md +56 -0
- package/skills/grok-game-ui-icons/agentsam.skill.json +25 -0
- package/skills/grok-generate2dmap/LICENSE +21 -0
- package/skills/grok-generate2dmap/SKILL.md +120 -0
- package/skills/grok-generate2dmap/SOURCE.md +6 -0
- package/skills/grok-generate2dmap/agentsam.skill.json +25 -0
- package/skills/grok-generate2dmap/references/deliverables.md +101 -0
- package/skills/grok-generate2dmap/references/layered-map-contract.md +209 -0
- package/skills/grok-generate2dmap/references/map-strategies.md +341 -0
- package/skills/grok-generate2dmap/references/object-production-gate.md +24 -0
- package/skills/grok-generate2dmap/references/pipeline.md +149 -0
- package/skills/grok-generate2dmap/references/prop-pack-contract.md +174 -0
- package/skills/grok-generate2dmap/references/side-scroll-stages.md +29 -0
- package/skills/grok-generate2dmap/scripts/compose_layered_preview.py +157 -0
- package/skills/grok-generate2dmap/scripts/extract_prop_pack.py +369 -0
- package/skills/grok-generate2dsprite/LICENSE +21 -0
- package/skills/grok-generate2dsprite/SKILL.md +313 -0
- package/skills/grok-generate2dsprite/SOURCE.md +6 -0
- package/skills/grok-generate2dsprite/agentsam.skill.json +25 -0
- package/skills/grok-generate2dsprite/references/modes.md +124 -0
- package/skills/grok-generate2dsprite/references/prompt-rules.md +315 -0
- package/skills/grok-generate2dsprite/scripts/generate2dsprite.py +1031 -0
- package/skills/grok-generate2dsprite/scripts/make_layout_guide.py +109 -0
- package/skills/grok-imagine/SKILL.md +201 -0
- package/skills/grok-imagine/agentsam.skill.json +25 -0
- package/skills/grok-multiplayer-p2p/SKILL.md +111 -0
- package/skills/grok-multiplayer-p2p/agentsam.skill.json +25 -0
- package/skills/grok-multiplayer-p2p/references/react-binding.md +144 -0
- package/skills/grok-multiplayer-p2p/references/signaling-relay.md +269 -0
- package/skills/grok-neon/SKILL.md +157 -0
- package/skills/grok-neon/agentsam.skill.json +25 -0
- package/skills/grok-og/SKILL.md +102 -0
- package/skills/grok-og/agentsam.skill.json +25 -0
- package/skills/grok-og/references/brand-pass.md +53 -0
- package/skills/grok-og/references/custom-card.md +107 -0
- package/skills/grok-og/references/favicon-and-icons.md +48 -0
- package/skills/grok-og/references/og-type-contract.md +18 -0
- package/skills/grok-og/references/placeholder-card.md +21 -0
- package/skills/grok-og/references/x-banner.md +41 -0
- package/skills/grok-threejs/SKILL.md +67 -0
- package/skills/grok-threejs/agentsam.skill.json +25 -0
- package/skills/grok-threejs/references/llms-full.txt +2780 -0
- package/skills/grok-video2dsprite/LICENSE +21 -0
- package/skills/grok-video2dsprite/SKILL.md +236 -0
- package/skills/grok-video2dsprite/SOURCE.md +6 -0
- package/skills/grok-video2dsprite/agentsam.skill.json +25 -0
- package/skills/grok-video2dsprite/references/pipeline.md +90 -0
- package/skills/grok-video2dsprite/references/prompt-rules.md +63 -0
- package/skills/grok-video2dsprite/scripts/video2dsprite.py +490 -0
- package/skills/grok-xai-api/SKILL.md +152 -0
- package/skills/grok-xai-api/agentsam.skill.json +25 -0
- package/src/cli/command-catalog.js +46 -4
- package/src/cli/dispatch.js +1 -0
- package/src/cli.js +10 -0
- package/src/cloudflare/runtime-status.js +2 -1
- package/src/commands/autorag.js +67 -13
- package/src/commands/cheat-sheet.js +29 -4
- package/src/commands/codebaseindex.js +45 -2
- package/src/commands/knowledge.js +59 -2
- package/src/commands/machine-binary.js +103 -0
- package/src/commands/machine.js +137 -0
- package/src/commands/security.js +67 -4
- package/src/commands/setup.js +33 -16
- package/src/commands/status.js +8 -1
- package/src/features/resolve.js +1 -0
- package/src/knowledge/engine.js +21 -2
- package/src/knowledge/runtime-discovery.js +127 -0
- package/src/lib/identity-scaffold.js +53 -13
- package/src/lib/setup/recipes.js +1 -1
- package/src/models/discovery.js +40 -22
- package/src/models/inventory-core.d.ts +105 -0
- package/src/models/inventory-core.js +54 -6
- package/src/security/logs.js +3 -0
- package/src/ui/cli/help.js +41 -0
- package/test/apps-scaffold-contract.test.mjs +3 -2
- package/test/cli/api-key-env-whoami.test.mjs +2 -2
- package/test/cli/catalog-authority.test.mjs +64 -0
- package/test/cli/machine-command.test.mjs +34 -0
- package/test/fixtures/project-authority/customer-app/.agentsam/app.json +5 -0
- package/test/fixtures/project-authority/customer-app/apps/my-product/index.ts +1 -0
- package/test/fixtures/project-authority/customer-app/wrangler.toml +17 -0
- package/test/integration/autorag-project-authority.integration.test.mjs +51 -0
- package/test/integration/autorag-runtime-discovery.test.mjs +107 -0
- package/test/integration/cms-app-scaffold.test.mjs +37 -31
- package/test/integration/cms-site-tenancy-contract.test.mjs +28 -17
- package/test/integration/codebaseindex-ingest.test.mjs +41 -0
- package/test/integration/database-hosted-honesty.test.mjs +49 -0
- package/test/integration/desktop-keychain-boundary.test.mjs +45 -0
- package/test/integration/hosted-work-host-authority.test.mjs +47 -0
- package/test/integration/inventory-eligibility.test.mjs +59 -0
- package/test/integration/local-provider-bridge.test.mjs +65 -0
- package/test/integration/local-studio-settings-production.test.mjs +40 -0
- package/test/integration/lockfile-sync.test.mjs +26 -0
- package/test/integration/provider-env-cli.test.mjs +13 -3
- package/test/integration/runtime-status.integration.test.mjs +16 -1
- package/test/integration/theme-package-registry.test.mjs +31 -0
- package/test/repository-snapshot-view.test.mjs +2 -2
- package/test/security.test.mjs +14 -0
- package/test/terminal-machine-identity.test.mjs +2 -2
- package/apps/client-cms-editor/backend/src/demo-bootstrap.ts +0 -145
- package/packages/agentsam-nav/dist/Nav.d.ts +0 -43
- package/packages/agentsam-nav/dist/NavCollapsible.d.ts +0 -10
- package/packages/agentsam-nav/dist/NavContent.d.ts +0 -2
- package/packages/agentsam-nav/dist/NavFooter.d.ts +0 -2
- package/packages/agentsam-nav/dist/NavGroup.d.ts +0 -3
- package/packages/agentsam-nav/dist/NavHeader.d.ts +0 -2
- package/packages/agentsam-nav/dist/NavMenu.d.ts +0 -22
- package/packages/agentsam-nav/dist/NavProvider.d.ts +0 -35
- package/packages/agentsam-nav/dist/NavTrigger.d.ts +0 -2
- package/packages/agentsam-nav/dist/Surfaces.d.ts +0 -41
- package/packages/agentsam-nav/dist/browser.d.ts +0 -11
- package/packages/agentsam-nav/dist/browser.global.js +0 -132
- package/packages/agentsam-nav/dist/browser.js +0 -613
- package/packages/agentsam-nav/dist/contracts.d.ts +0 -76
- package/packages/agentsam-nav/dist/index.d.ts +0 -11
- package/packages/agentsam-nav/dist/index.js +0 -614
- package/packages/agentsam-nav/dist/legacy.css +0 -83
- package/packages/agentsam-nav/dist/shell.css +0 -113
- package/packages/agentsam-nav/dist/theme.css +0 -113
- package/packages/agentsam-nav/dist/utils.d.ts +0 -1
- package/packages/agentsam-repository/src/goap.js.bak-20260922011251 +0 -670
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native (desktop) OAuth handoff.
|
|
3
|
+
*
|
|
4
|
+
* A desktop app signs in through the system browser, so the provider redirect
|
|
5
|
+
* lands in a browser that cannot hand a Set-Cookie to the app. Instead:
|
|
6
|
+
*
|
|
7
|
+
* start GET /api/oauth/{provider}/start?client=native
|
|
8
|
+
* &native_challenge=<S256(verifier)>&native_redirect=<deep link>
|
|
9
|
+
* callback creates an auth_sessions row (type=desktop), stores a single-use
|
|
10
|
+
* handoff bound to the PKCE challenge, and 302s to the deep link
|
|
11
|
+
* with ?handoff=<one-time code> — the session id never rides a URL.
|
|
12
|
+
* exchange POST /api/oauth/native/exchange { handoff, code_verifier }
|
|
13
|
+
* → same JSON as native password login (session_id, expires_at, user)
|
|
14
|
+
*
|
|
15
|
+
* Identity only. This never writes provider resource grants (user_oauth_tokens).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { jsonResponse } from '../core/http-json.js';
|
|
19
|
+
import { NATIVE_HANDOFF_TTL_SECONDS, SESSION_TYPES } from '../core/session-policy.js';
|
|
20
|
+
import { pkceChallenge, randomOAuthState } from './pkce.js';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_NATIVE_SCHEMES = ['agentsamstudio'];
|
|
23
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]']);
|
|
24
|
+
const S256_CHALLENGE = /^[A-Za-z0-9_-]{43}$/;
|
|
25
|
+
|
|
26
|
+
export const NATIVE_EXCHANGE_PATH = '/api/oauth/native/exchange';
|
|
27
|
+
|
|
28
|
+
async function sha256Hex(value) {
|
|
29
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(String(value)));
|
|
30
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function allowedSchemes(env) {
|
|
34
|
+
const configured = String(env?.IDENTITY_NATIVE_REDIRECT_SCHEMES || '')
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((s) => s.trim().toLowerCase())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
return configured.length ? configured : DEFAULT_NATIVE_SCHEMES;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Deep-link scheme allowlist, or an http loopback redirect. Never a web origin. */
|
|
42
|
+
export function isAllowedNativeRedirect(uri, env) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = new URL(String(uri || ''));
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (parsed.protocol === 'http:') return LOOPBACK_HOSTS.has(parsed.hostname);
|
|
50
|
+
return allowedSchemes(env).includes(parsed.protocol.replace(/:$/, '').toLowerCase());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Read native params off an OAuth start URL.
|
|
55
|
+
* @returns {{ native: false } | { native: true, error: string } |
|
|
56
|
+
* { native: true, clientType: 'desktop', nativeChallenge: string, nativeRedirect: string }}
|
|
57
|
+
*/
|
|
58
|
+
export function parseNativeStart(url, env) {
|
|
59
|
+
const client = String(url.searchParams.get('client') || '').toLowerCase();
|
|
60
|
+
if (client !== 'native' && client !== 'desktop') return { native: false };
|
|
61
|
+
const nativeChallenge = url.searchParams.get('native_challenge') || '';
|
|
62
|
+
const nativeRedirect = url.searchParams.get('native_redirect') || '';
|
|
63
|
+
if (!S256_CHALLENGE.test(nativeChallenge)) return { native: true, error: 'native_challenge_invalid' };
|
|
64
|
+
if (!isAllowedNativeRedirect(nativeRedirect, env)) return { native: true, error: 'native_redirect_not_allowed' };
|
|
65
|
+
return { native: true, clientType: SESSION_TYPES.DESKTOP, nativeChallenge, nativeRedirect };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Session type an OAuth transaction should mint. */
|
|
69
|
+
export function sessionTypeForTransaction(saved) {
|
|
70
|
+
return saved?.client_type === SESSION_TYPES.DESKTOP ? SESSION_TYPES.DESKTOP : SESSION_TYPES.BROWSER;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Shared callback tail for every OAuth lane. Browser transactions keep the
|
|
75
|
+
* existing cookie + globe redirect; native transactions get the handoff redirect.
|
|
76
|
+
*/
|
|
77
|
+
export async function finishOAuthLogin({ request, env, identity, adapter, saved, result, loginPath }) {
|
|
78
|
+
const url = new URL(request.url);
|
|
79
|
+
const redirectTo = identity.resolvePostLoginPath(saved?.redirect_to);
|
|
80
|
+
|
|
81
|
+
if (sessionTypeForTransaction(saved) === SESSION_TYPES.DESKTOP) {
|
|
82
|
+
if (!saved.native_challenge || !isAllowedNativeRedirect(saved.native_redirect, env)) {
|
|
83
|
+
await adapter.revokeSession?.(result.sessionId);
|
|
84
|
+
return Response.redirect(`${url.origin}${loginPath}?error=native_handoff_invalid`, 302);
|
|
85
|
+
}
|
|
86
|
+
const handoff = randomOAuthState();
|
|
87
|
+
await adapter.createNativeHandoff({
|
|
88
|
+
handoffHash: await sha256Hex(handoff),
|
|
89
|
+
sessionId: result.sessionId,
|
|
90
|
+
challenge: saved.native_challenge,
|
|
91
|
+
ttlSeconds: NATIVE_HANDOFF_TTL_SECONDS,
|
|
92
|
+
});
|
|
93
|
+
const target = new URL(saved.native_redirect);
|
|
94
|
+
target.searchParams.set('handoff', handoff);
|
|
95
|
+
return new Response(null, {
|
|
96
|
+
status: 302,
|
|
97
|
+
headers: { Location: target.toString(), 'Cache-Control': 'no-store' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const res = identity.buildLoginSuccessResponse(request, result.sessionId, redirectTo);
|
|
102
|
+
const globeUrl = `${url.origin}${loginPath}?globe_exit=1&next=${encodeURIComponent(redirectTo)}`;
|
|
103
|
+
return new Response(null, {
|
|
104
|
+
status: 302,
|
|
105
|
+
headers: { Location: globeUrl, 'Set-Cookie': res.headers.get('Set-Cookie') || '' },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** POST /api/oauth/native/exchange — single-use, PKCE-bound. */
|
|
110
|
+
export async function handleNativeExchangeRequest(request, { adapter, identity }) {
|
|
111
|
+
if (request.method !== 'POST') return jsonResponse({ ok: false, error: 'method_not_allowed' }, 405);
|
|
112
|
+
let body;
|
|
113
|
+
try {
|
|
114
|
+
body = await request.json();
|
|
115
|
+
} catch {
|
|
116
|
+
return jsonResponse({ ok: false, error: 'invalid_json' }, 400);
|
|
117
|
+
}
|
|
118
|
+
const handoff = String(body?.handoff || '').trim();
|
|
119
|
+
const verifier = String(body?.code_verifier || body?.codeVerifier || '').trim();
|
|
120
|
+
if (!handoff || !verifier) {
|
|
121
|
+
return jsonResponse({ ok: false, error: 'handoff_and_code_verifier_required' }, 400);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Consumed before the verifier check, so a wrong verifier burns the code.
|
|
125
|
+
const row = await adapter.consumeNativeHandoff(await sha256Hex(handoff));
|
|
126
|
+
if (!row) return jsonResponse({ ok: false, error: 'handoff_invalid_or_expired' }, 400);
|
|
127
|
+
if ((await pkceChallenge(verifier)) !== row.challenge) {
|
|
128
|
+
await adapter.revokeSession?.(row.session_id);
|
|
129
|
+
return jsonResponse({ ok: false, error: 'pkce_verification_failed' }, 400);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const session = await adapter.getSession(row.session_id);
|
|
133
|
+
const user = session ? await adapter.findUserById(session.user_id) : null;
|
|
134
|
+
if (!session || !user) return jsonResponse({ ok: false, error: 'session_unavailable' }, 401);
|
|
135
|
+
return identity.buildNativeLoginSuccessResponse({ sessionId: session.id, session, user }, body?.next);
|
|
136
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** ChatGPT Apps SDK / OpenAI hosted identity header constants. */
|
|
2
|
+
|
|
3
|
+
export const CHATGPT_USER_EMAIL_HEADER = 'oai-authenticated-user-email';
|
|
4
|
+
export const CHATGPT_USER_FULL_NAME_HEADER = 'oai-authenticated-user-full-name';
|
|
5
|
+
export const CHATGPT_USER_FULL_NAME_ENCODING_HEADER =
|
|
6
|
+
'oai-authenticated-user-full-name-encoding';
|
|
7
|
+
export const CHATGPT_PERCENT_ENCODED_UTF8 = 'percent-encoded-utf-8';
|
|
8
|
+
|
|
9
|
+
export const CHATGPT_SIGN_IN_PATH = '/signin-with-chatgpt';
|
|
10
|
+
export const CHATGPT_SIGN_OUT_PATH = '/signout-with-chatgpt';
|
|
11
|
+
export const CHATGPT_CALLBACK_PATH = '/callback';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Headers | Record<string, string | string[] | undefined> | null | undefined} headers
|
|
15
|
+
* @param {string} name
|
|
16
|
+
*/
|
|
17
|
+
export function readHeader(headers, name) {
|
|
18
|
+
if (!headers) return null;
|
|
19
|
+
const key = String(name).toLowerCase();
|
|
20
|
+
if (typeof headers.get === 'function') {
|
|
21
|
+
const value = headers.get(name) ?? headers.get(key);
|
|
22
|
+
return value == null ? null : String(value);
|
|
23
|
+
}
|
|
24
|
+
const raw =
|
|
25
|
+
headers[name] ??
|
|
26
|
+
headers[key] ??
|
|
27
|
+
Object.entries(headers).find(([k]) => k.toLowerCase() === key)?.[1];
|
|
28
|
+
if (Array.isArray(raw)) return raw[0] == null ? null : String(raw[0]);
|
|
29
|
+
return raw == null ? null : String(raw);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function safeDecodeURIComponent(value) {
|
|
33
|
+
try {
|
|
34
|
+
return decodeURIComponent(value);
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract ChatGPT hosted user profile from request headers (Apps SDK).
|
|
42
|
+
* Does not leak transport headers into NormalizedExternalIdentity.raw beyond a redacted snapshot.
|
|
43
|
+
*
|
|
44
|
+
* @param {Headers | Record<string, string | string[] | undefined> | null | undefined} headers
|
|
45
|
+
* @returns {{ email: string, fullName: string | null, displayName: string } | null}
|
|
46
|
+
*/
|
|
47
|
+
export function readChatGptUserFromHeaders(headers) {
|
|
48
|
+
const email = readHeader(headers, CHATGPT_USER_EMAIL_HEADER);
|
|
49
|
+
if (!email) return null;
|
|
50
|
+
|
|
51
|
+
const encodedFullName = readHeader(headers, CHATGPT_USER_FULL_NAME_HEADER);
|
|
52
|
+
const encoding = readHeader(headers, CHATGPT_USER_FULL_NAME_ENCODING_HEADER);
|
|
53
|
+
const fullName =
|
|
54
|
+
encodedFullName && encoding === CHATGPT_PERCENT_ENCODED_UTF8
|
|
55
|
+
? safeDecodeURIComponent(encodedFullName)
|
|
56
|
+
: encodedFullName && encoding !== CHATGPT_PERCENT_ENCODED_UTF8
|
|
57
|
+
? encodedFullName
|
|
58
|
+
: null;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
email: email.trim().toLowerCase(),
|
|
62
|
+
fullName,
|
|
63
|
+
displayName: (fullName || email).trim(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createIdentityProvider } from '../../provider-contract.js';
|
|
2
|
+
import { normalizeChatGptIdentity } from './mapper.js';
|
|
3
|
+
import { readChatGptUserFromHeaders } from './headers.js';
|
|
4
|
+
import { chatGPTSignInPath, chatGPTSignOutPath, safeRelativeReturnPath } from './paths.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ChatGPT hosted identity provider (OpenAI Apps SDK).
|
|
8
|
+
*
|
|
9
|
+
* Auth is header-based in the ChatGPT host — not classic OAuth client credentials.
|
|
10
|
+
* `authorizeUrl` points at the hosted sign-in path; `exchangeCode` is unsupported.
|
|
11
|
+
*/
|
|
12
|
+
export const ChatGptProvider = createIdentityProvider({
|
|
13
|
+
id: 'chatgpt',
|
|
14
|
+
authorizeUrl: ({ redirectUri, state } = {}) => {
|
|
15
|
+
const returnTo =
|
|
16
|
+
typeof redirectUri === 'string' && redirectUri.startsWith('/')
|
|
17
|
+
? redirectUri
|
|
18
|
+
: '/';
|
|
19
|
+
const path = chatGPTSignInPath(returnTo);
|
|
20
|
+
return state ? `${path}&state=${encodeURIComponent(state)}` : path;
|
|
21
|
+
},
|
|
22
|
+
exchangeCode: async () => null,
|
|
23
|
+
getProfile: async () => null,
|
|
24
|
+
normalizeIdentity: normalizeChatGptIdentity,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a portable CmsAuthHost for ChatGPT-hosted apps.
|
|
29
|
+
* Reads `oai-authenticated-*` headers; challenges to hosted sign-in when missing.
|
|
30
|
+
*
|
|
31
|
+
* @param {{ cmsBase?: string }} [options]
|
|
32
|
+
*/
|
|
33
|
+
export function createChatGptCmsAuthHost(options = {}) {
|
|
34
|
+
const cmsBase = options.cmsBase || '/cms';
|
|
35
|
+
return {
|
|
36
|
+
async authorizeCmsAccess({ path, headers }) {
|
|
37
|
+
const user = readChatGptUserFromHeaders(headers);
|
|
38
|
+
if (!user) {
|
|
39
|
+
return {
|
|
40
|
+
allowed: false,
|
|
41
|
+
principal: null,
|
|
42
|
+
reason: 'chatgpt_identity_required',
|
|
43
|
+
challengeUrl: chatGPTSignInPath(path || cmsBase),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const identity = normalizeChatGptIdentity({
|
|
47
|
+
email: user.email,
|
|
48
|
+
name: user.fullName || user.displayName,
|
|
49
|
+
sub: user.email,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
allowed: true,
|
|
53
|
+
principal: {
|
|
54
|
+
id: `chatgpt:${identity.subject}`,
|
|
55
|
+
displayName: identity.name || identity.email || identity.subject,
|
|
56
|
+
email: identity.email || undefined,
|
|
57
|
+
roles: ['cms_admin'],
|
|
58
|
+
localDevelopment: false,
|
|
59
|
+
provider: 'chatgpt',
|
|
60
|
+
},
|
|
61
|
+
reason: 'chatgpt_hosted_identity',
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
buildLoginUrl({ returnTo }) {
|
|
65
|
+
return chatGPTSignInPath(returnTo || cmsBase);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
normalizeChatGptIdentity,
|
|
72
|
+
readChatGptUserFromHeaders,
|
|
73
|
+
chatGPTSignInPath,
|
|
74
|
+
chatGPTSignOutPath,
|
|
75
|
+
safeRelativeReturnPath,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
CHATGPT_USER_EMAIL_HEADER,
|
|
80
|
+
CHATGPT_USER_FULL_NAME_HEADER,
|
|
81
|
+
CHATGPT_USER_FULL_NAME_ENCODING_HEADER,
|
|
82
|
+
CHATGPT_PERCENT_ENCODED_UTF8,
|
|
83
|
+
CHATGPT_SIGN_IN_PATH,
|
|
84
|
+
CHATGPT_SIGN_OUT_PATH,
|
|
85
|
+
CHATGPT_CALLBACK_PATH,
|
|
86
|
+
} from './headers.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { normalizeExternalIdentity } from '../../contracts/external-identity.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize ChatGPT / OpenAI Apps SDK identity into portable external identity.
|
|
5
|
+
* Provider id is always `chatgpt` — never leak `oai-authenticated-*` header names into contracts.
|
|
6
|
+
*
|
|
7
|
+
* @param {Record<string, unknown> | null | undefined} profile
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeChatGptIdentity(profile) {
|
|
10
|
+
const raw = profile && typeof profile === 'object' ? profile : {};
|
|
11
|
+
const email =
|
|
12
|
+
typeof raw.email === 'string' && raw.email.trim()
|
|
13
|
+
? raw.email.trim().toLowerCase()
|
|
14
|
+
: null;
|
|
15
|
+
const subject = String(raw.sub || raw.id || raw.subject || email || '').trim();
|
|
16
|
+
if (!subject) {
|
|
17
|
+
throw new Error('chatgpt_identity_requires_subject');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const name =
|
|
21
|
+
typeof raw.name === 'string' && raw.name.trim()
|
|
22
|
+
? raw.name.trim()
|
|
23
|
+
: typeof raw.displayName === 'string' && raw.displayName.trim()
|
|
24
|
+
? raw.displayName.trim()
|
|
25
|
+
: typeof raw.fullName === 'string' && raw.fullName.trim()
|
|
26
|
+
? raw.fullName.trim()
|
|
27
|
+
: null;
|
|
28
|
+
|
|
29
|
+
return normalizeExternalIdentity({
|
|
30
|
+
provider: 'chatgpt',
|
|
31
|
+
subject,
|
|
32
|
+
email,
|
|
33
|
+
emailVerified: Boolean(email),
|
|
34
|
+
name,
|
|
35
|
+
avatar: null,
|
|
36
|
+
username: null,
|
|
37
|
+
raw: {
|
|
38
|
+
source: 'chatgpt_hosted',
|
|
39
|
+
email,
|
|
40
|
+
name,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHATGPT_CALLBACK_PATH,
|
|
3
|
+
CHATGPT_SIGN_IN_PATH,
|
|
4
|
+
CHATGPT_SIGN_OUT_PATH,
|
|
5
|
+
} from './headers.js';
|
|
6
|
+
|
|
7
|
+
function isReservedAuthPath(pathname) {
|
|
8
|
+
return (
|
|
9
|
+
pathname === CHATGPT_SIGN_IN_PATH ||
|
|
10
|
+
pathname === CHATGPT_SIGN_OUT_PATH ||
|
|
11
|
+
pathname === CHATGPT_CALLBACK_PATH
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Safe relative return path for ChatGPT hosted sign-in/out redirects. */
|
|
16
|
+
export function safeRelativeReturnPath(value) {
|
|
17
|
+
const raw = String(value || '/');
|
|
18
|
+
if (!raw.startsWith('/') || raw.startsWith('//')) return '/';
|
|
19
|
+
|
|
20
|
+
let url;
|
|
21
|
+
try {
|
|
22
|
+
url = new URL(raw, 'https://app.local');
|
|
23
|
+
} catch {
|
|
24
|
+
return '/';
|
|
25
|
+
}
|
|
26
|
+
if (url.origin !== 'https://app.local') return '/';
|
|
27
|
+
if (isReservedAuthPath(url.pathname)) return '/';
|
|
28
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function chatGPTSignInPath(returnTo = '/') {
|
|
32
|
+
const safeReturnTo = safeRelativeReturnPath(returnTo);
|
|
33
|
+
return `${CHATGPT_SIGN_IN_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function chatGPTSignOutPath(returnTo = '/') {
|
|
37
|
+
const safeReturnTo = safeRelativeReturnPath(returnTo);
|
|
38
|
+
return `${CHATGPT_SIGN_OUT_PATH}?return_to=${encodeURIComponent(safeReturnTo)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
CHATGPT_SIGN_IN_PATH,
|
|
43
|
+
CHATGPT_SIGN_OUT_PATH,
|
|
44
|
+
CHATGPT_CALLBACK_PATH,
|
|
45
|
+
};
|
|
@@ -2,12 +2,11 @@ const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
|
|
|
2
2
|
const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
|
|
3
3
|
|
|
4
4
|
/** @param {import('../../provider-contract.js').OAuthAuthorizeInput} input */
|
|
5
|
-
export function getGithubAuthUrl({ clientId, redirectUri, state, codeChallenge
|
|
5
|
+
export function getGithubAuthUrl({ clientId, redirectUri, state, codeChallenge } = {}) {
|
|
6
6
|
const url = new URL(GITHUB_AUTH_URL);
|
|
7
7
|
url.searchParams.set('client_id', clientId || '');
|
|
8
8
|
url.searchParams.set('redirect_uri', redirectUri || '');
|
|
9
9
|
url.searchParams.set('response_type', 'code');
|
|
10
|
-
url.searchParams.set('scope', scope || 'read:user user:email');
|
|
11
10
|
url.searchParams.set('state', state || '');
|
|
12
11
|
if (codeChallenge) {
|
|
13
12
|
url.searchParams.set('code_challenge', codeChallenge);
|
|
@@ -3,7 +3,8 @@ const GITHUB_EMAILS_URL = 'https://api.github.com/user/emails';
|
|
|
3
3
|
|
|
4
4
|
const GITHUB_HEADERS = {
|
|
5
5
|
Accept: 'application/vnd.github+json',
|
|
6
|
-
'X-GitHub-Api-Version': '
|
|
6
|
+
'X-GitHub-Api-Version': '2026-03-10',
|
|
7
|
+
'User-Agent': 'AgentSam-Local-Studio',
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
/** @param {string} accessToken */
|
|
@@ -17,7 +18,15 @@ export async function fetchGithubProfile(accessToken) {
|
|
|
17
18
|
headers: { Authorization: `Bearer ${accessToken}`, ...GITHUB_HEADERS },
|
|
18
19
|
}),
|
|
19
20
|
]);
|
|
20
|
-
if (!userRes.ok)
|
|
21
|
+
if (!userRes.ok) {
|
|
22
|
+
const detail = (await userRes.text()).slice(0, 500);
|
|
23
|
+
console.error('github_userinfo_failed', {
|
|
24
|
+
status: userRes.status,
|
|
25
|
+
requestId: userRes.headers.get('x-github-request-id'),
|
|
26
|
+
detail,
|
|
27
|
+
});
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
21
30
|
const user = await userRes.json();
|
|
22
31
|
let email = null;
|
|
23
32
|
let emailVerified = false;
|
|
@@ -26,6 +35,11 @@ export async function fetchGithubProfile(accessToken) {
|
|
|
26
35
|
const primary = Array.isArray(emails) ? emails.find((e) => e?.primary) : null;
|
|
27
36
|
email = primary?.email || null;
|
|
28
37
|
emailVerified = !!primary?.verified;
|
|
38
|
+
} else {
|
|
39
|
+
console.warn('github_user_emails_failed', {
|
|
40
|
+
status: emailRes.status,
|
|
41
|
+
requestId: emailRes.headers.get('x-github-request-id'),
|
|
42
|
+
});
|
|
29
43
|
}
|
|
30
44
|
return { ...user, email, email_verified: emailVerified };
|
|
31
45
|
} catch {
|
|
@@ -5,11 +5,12 @@ import { GcpProvider } from './gcp/index.js';
|
|
|
5
5
|
import { EmailProvider } from './email/index.js';
|
|
6
6
|
import { IamProvider } from './iam/index.js';
|
|
7
7
|
import { CloudflareProvider } from './cloudflare/index.js';
|
|
8
|
+
import { ChatGptProvider } from './chatgpt/index.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Active identity provider registry.
|
|
11
|
-
* `inneranimalmedia` is the canonical platform template id; `iam`
|
|
12
|
-
* legacy lookup
|
|
12
|
+
* `inneranimalmedia` is the canonical platform template id; `iam` and `inneranimal` remain
|
|
13
|
+
* legacy lookup aliases (OAuth/DB provider keys on platform identities).
|
|
13
14
|
* @type {Record<string, import('../provider-contract.js').IdentityProvider>}
|
|
14
15
|
*/
|
|
15
16
|
export const identityProviders = Object.freeze({
|
|
@@ -20,12 +21,18 @@ export const identityProviders = Object.freeze({
|
|
|
20
21
|
inneranimalmedia: IamProvider,
|
|
21
22
|
iam: IamProvider,
|
|
22
23
|
cloudflare: CloudflareProvider,
|
|
24
|
+
chatgpt: ChatGptProvider,
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
/** @param {string} id */
|
|
26
28
|
export function getIdentityProvider(id) {
|
|
27
29
|
const key = String(id || '').trim().toLowerCase();
|
|
28
|
-
if (key === 'iam'
|
|
30
|
+
if (key === 'iam' || key === 'inneranimal') {
|
|
31
|
+
return identityProviders.inneranimalmedia || identityProviders.iam || null;
|
|
32
|
+
}
|
|
33
|
+
if (key === 'openai' || key === 'chatgpt-hosted' || key === 'chatgpt_hosted') {
|
|
34
|
+
return identityProviders.chatgpt || null;
|
|
35
|
+
}
|
|
29
36
|
return identityProviders[key] || null;
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -40,4 +47,5 @@ export {
|
|
|
40
47
|
EmailProvider,
|
|
41
48
|
IamProvider,
|
|
42
49
|
CloudflareProvider,
|
|
50
|
+
ChatGptProvider,
|
|
43
51
|
};
|
|
@@ -130,7 +130,7 @@ export function createIdentityService(config) {
|
|
|
130
130
|
return { ok: true };
|
|
131
131
|
},
|
|
132
132
|
|
|
133
|
-
async provisionOAuthUser({ provider, providerSubject, email, displayName }) {
|
|
133
|
+
async provisionOAuthUser({ provider, providerSubject, email, displayName, sessionType }) {
|
|
134
134
|
let user = await adapter.findUserByProvider(provider, providerSubject);
|
|
135
135
|
if (!user && email) {
|
|
136
136
|
user = await adapter.findUserByEmail(email);
|
|
@@ -155,6 +155,7 @@ export function createIdentityService(config) {
|
|
|
155
155
|
displayName: displayName || user.display_name,
|
|
156
156
|
provider,
|
|
157
157
|
providerSubject,
|
|
158
|
+
type: sessionType,
|
|
158
159
|
});
|
|
159
160
|
return { ok: true, authUserId: user.id, sessionId: session.id, session };
|
|
160
161
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { createCloudflareD1Adapter } from '../adapters/
|
|
1
|
+
import { createCloudflareD1Adapter, createIamCompatIdentityAdapter } from '../adapters/iam-compat/index.js';
|
|
2
|
+
import { createPortableD1IdentityAdapter } from '../adapters/portable-d1/index.js';
|
|
2
3
|
import { createIdentityService } from './identity-service.js';
|
|
3
4
|
import { jsonResponse } from '../core/http-json.js';
|
|
4
5
|
import { hashPassword } from '../core/password-crypto.js';
|
|
@@ -12,9 +13,31 @@ import { fetchCloudflareProfile } from '../providers/cloudflare/profile.js';
|
|
|
12
13
|
import { IDENTITY_ROUTE_IDS } from '../contracts/route-ids.js';
|
|
13
14
|
import { IdentityRoutingError } from '../contracts/identity-store.js';
|
|
14
15
|
import { resolveOAuthCredentialLane } from '../oauth/credentials.js';
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
GOOGLE_DESKTOP_LOGIN_EXCHANGE_PATH,
|
|
18
|
+
handleGoogleDesktopExchangeRequest,
|
|
19
|
+
handleGoogleDesktopLoginExchangeRequest,
|
|
20
|
+
} from '../oauth/google-desktop-exchange.js';
|
|
16
21
|
import { iamPlatformOAuthCallback, iamPlatformOAuthStart } from '../oauth/iam-platform.js';
|
|
17
22
|
import { pkceChallenge, pkceVerifier, randomOAuthState } from '../oauth/pkce.js';
|
|
23
|
+
import {
|
|
24
|
+
NATIVE_EXCHANGE_PATH,
|
|
25
|
+
finishOAuthLogin,
|
|
26
|
+
handleNativeExchangeRequest,
|
|
27
|
+
parseNativeStart,
|
|
28
|
+
sessionTypeForTransaction,
|
|
29
|
+
} from '../oauth/native-handoff.js';
|
|
30
|
+
|
|
31
|
+
function resolveIdentityAdapter(env, options = {}) {
|
|
32
|
+
if (options.adapter) return options.adapter;
|
|
33
|
+
const profile = String(options.identityProfile || env?.IDENTITY_ADAPTER_PROFILE || 'iam-compat')
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase();
|
|
36
|
+
if (profile === 'portable' || profile === 'portable-d1') {
|
|
37
|
+
return createPortableD1IdentityAdapter(env.DB);
|
|
38
|
+
}
|
|
39
|
+
return createIamCompatIdentityAdapter(env.DB);
|
|
40
|
+
}
|
|
18
41
|
|
|
19
42
|
function escapeHtml(value) {
|
|
20
43
|
return String(value ?? '')
|
|
@@ -51,7 +74,12 @@ function buildPasswordResetService(env, adapter, options = {}) {
|
|
|
51
74
|
}
|
|
52
75
|
const company = await adapter.getDefaultCompany().catch(() => null);
|
|
53
76
|
const brand = company?.name || 'Your App';
|
|
54
|
-
const fromEmail = company?.supportEmail
|
|
77
|
+
const fromEmail = company?.supportEmail;
|
|
78
|
+
if (!fromEmail) {
|
|
79
|
+
const err = new Error('email_not_configured');
|
|
80
|
+
err.code = 'email_not_configured';
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
55
83
|
const html = `<p>Hi ${escapeHtml(name)},</p><p>Your ${escapeHtml(brand)} verification code is:</p><p style="font-size:22px;font-weight:700;letter-spacing:4px;">${escapeHtml(code)}</p><p>Enter this on the reset page. Expires in 15 minutes.</p>`;
|
|
56
84
|
const res = await fetch('https://api.resend.com/emails', {
|
|
57
85
|
method: 'POST',
|
|
@@ -78,14 +106,15 @@ function buildPasswordResetService(env, adapter, options = {}) {
|
|
|
78
106
|
* Worker fetch handler for identity API + auth page routing.
|
|
79
107
|
* @param {Request} request
|
|
80
108
|
* @param {{ DB: import('../adapters/cloudflare-d1/index.js').D1Database, ASSETS?: { fetch: (req: Request) => Promise<Response> }, [key: string]: unknown }} env
|
|
81
|
-
* @param {{ identity?: ReturnType<typeof createIdentityService>, brandName?: string }} [options]
|
|
109
|
+
* @param {{ identity?: ReturnType<typeof createIdentityService>, brandName?: string, companySlug?: string }} [options]
|
|
110
|
+
* companySlug — host-app portal branding key into D1 `company.slug` (never product-hardcoded here).
|
|
82
111
|
*/
|
|
83
112
|
export async function handleIdentityWorkerRequest(request, env, options = {}) {
|
|
84
113
|
const url = new URL(request.url);
|
|
85
114
|
const path = url.pathname;
|
|
86
115
|
const method = request.method.toUpperCase();
|
|
87
116
|
|
|
88
|
-
const adapter =
|
|
117
|
+
const adapter = resolveIdentityAdapter(env, options);
|
|
89
118
|
if (!options.identity && (!options.app?.id || !options.routeRegistry)) {
|
|
90
119
|
throw new IdentityRoutingError(
|
|
91
120
|
'AUTH_APP_UNRESOLVED',
|
|
@@ -156,6 +185,7 @@ export async function handleIdentityWorkerRequest(request, env, options = {}) {
|
|
|
156
185
|
if (!ctx) return jsonResponse({ ok: false, error: 'session_required' }, 401);
|
|
157
186
|
return jsonResponse({
|
|
158
187
|
ok: true,
|
|
188
|
+
authenticated: true,
|
|
159
189
|
user: {
|
|
160
190
|
id: ctx.user.id,
|
|
161
191
|
email: ctx.user.email,
|
|
@@ -165,8 +195,21 @@ export async function handleIdentityWorkerRequest(request, env, options = {}) {
|
|
|
165
195
|
}
|
|
166
196
|
|
|
167
197
|
if (path === '/api/company' && method === 'GET') {
|
|
168
|
-
|
|
169
|
-
const
|
|
198
|
+
// Portable: ?slug= → options.companySlug → env override → host match in company.meta.hosts → default
|
|
199
|
+
const slug = String(
|
|
200
|
+
url.searchParams.get('slug')
|
|
201
|
+
|| options.companySlug
|
|
202
|
+
|| env.IDENTITY_COMPANY_SLUG
|
|
203
|
+
|| '',
|
|
204
|
+
).trim() || undefined;
|
|
205
|
+
let company = null;
|
|
206
|
+
if (slug) {
|
|
207
|
+
company = await adapter.getCompanyBySlug(slug);
|
|
208
|
+
}
|
|
209
|
+
if (!company && typeof adapter.getCompanyByHost === 'function') {
|
|
210
|
+
company = await adapter.getCompanyByHost(url.hostname || request.headers.get('host'));
|
|
211
|
+
}
|
|
212
|
+
if (!company) company = await adapter.getDefaultCompany();
|
|
170
213
|
if (!company) return jsonResponse({ ok: false, error: 'company_not_found' }, 404);
|
|
171
214
|
return jsonResponse({ ok: true, company });
|
|
172
215
|
}
|
|
@@ -236,6 +279,12 @@ export async function handleIdentityWorkerRequest(request, env, options = {}) {
|
|
|
236
279
|
if (path === '/api/oauth/google/desktop-exchange') {
|
|
237
280
|
return handleGoogleDesktopExchangeRequest(request, env);
|
|
238
281
|
}
|
|
282
|
+
if (path === GOOGLE_DESKTOP_LOGIN_EXCHANGE_PATH) {
|
|
283
|
+
return handleGoogleDesktopLoginExchangeRequest(request, env, { adapter, identity });
|
|
284
|
+
}
|
|
285
|
+
if (path === NATIVE_EXCHANGE_PATH) {
|
|
286
|
+
return handleNativeExchangeRequest(request, { adapter, identity });
|
|
287
|
+
}
|
|
239
288
|
if (path === '/api/oauth/inneranimalmedia/callback' || path === '/api/oauth/iam/callback') {
|
|
240
289
|
if (method === 'GET') {
|
|
241
290
|
return iamPlatformOAuthCallback(request, env, adapter, identity);
|
|
@@ -332,12 +381,23 @@ async function oauthStart(request, env, identity, adapter, provider, creds) {
|
|
|
332
381
|
const redirectTo = identity.resolvePostLoginPath(
|
|
333
382
|
url.searchParams.get('next') || url.searchParams.get('return_to'),
|
|
334
383
|
);
|
|
384
|
+
const nativeStart = parseNativeStart(url, env);
|
|
385
|
+
if (nativeStart.error) {
|
|
386
|
+
return jsonResponse({ ok: false, error: nativeStart.error }, 400);
|
|
387
|
+
}
|
|
335
388
|
await adapter.saveOAuthState({
|
|
336
389
|
state,
|
|
337
390
|
provider,
|
|
338
391
|
codeVerifier,
|
|
339
392
|
redirectTo,
|
|
340
393
|
appId: identity.app?.id || null,
|
|
394
|
+
...(nativeStart.native
|
|
395
|
+
? {
|
|
396
|
+
clientType: nativeStart.clientType,
|
|
397
|
+
nativeChallenge: nativeStart.nativeChallenge,
|
|
398
|
+
nativeRedirect: nativeStart.nativeRedirect,
|
|
399
|
+
}
|
|
400
|
+
: {}),
|
|
341
401
|
});
|
|
342
402
|
|
|
343
403
|
const redirectUri = `${url.origin}/api/oauth/${provider}/callback`;
|
|
@@ -458,21 +518,14 @@ async function oauthCallback(request, env, identity, adapter, provider, creds) {
|
|
|
458
518
|
providerSubject: normalized.subject,
|
|
459
519
|
email: normalized.email,
|
|
460
520
|
displayName: normalized.name,
|
|
521
|
+
sessionType: sessionTypeForTransaction(saved),
|
|
461
522
|
});
|
|
462
523
|
await adapter.logAuthEvent({
|
|
463
524
|
userId: result.authUserId, eventType: 'login', status: 'ok', provider, request,
|
|
525
|
+
metadata: { session_type: sessionTypeForTransaction(saved) },
|
|
464
526
|
});
|
|
465
527
|
|
|
466
|
-
|
|
467
|
-
const res = identity.buildLoginSuccessResponse(request, result.sessionId, redirectTo);
|
|
468
|
-
const globeUrl = `${url.origin}${login}?globe_exit=1&next=${encodeURIComponent(redirectTo)}`;
|
|
469
|
-
return new Response(null, {
|
|
470
|
-
status: 302,
|
|
471
|
-
headers: {
|
|
472
|
-
Location: globeUrl,
|
|
473
|
-
'Set-Cookie': res.headers.get('Set-Cookie') || '',
|
|
474
|
-
},
|
|
475
|
-
});
|
|
528
|
+
return finishOAuthLogin({ request, env, identity, adapter, saved, result, loginPath: login });
|
|
476
529
|
} catch (error) {
|
|
477
530
|
const message = String(error?.message || error || 'oauth_callback_failed');
|
|
478
531
|
console.error('oauth_callback_failed', provider, message);
|
|
@@ -484,4 +537,4 @@ async function oauthCallback(request, env, identity, adapter, provider, creds) {
|
|
|
484
537
|
}
|
|
485
538
|
}
|
|
486
539
|
|
|
487
|
-
export { createIdentityService, createCloudflareD1Adapter };
|
|
540
|
+
export { createIdentityService, createCloudflareD1Adapter, createIamCompatIdentityAdapter, createPortableD1IdentityAdapter };
|