@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.4
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 +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- package/test/shell.test.mjs +4 -4
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host/app HTTP projection of semantic identity route IDs.
|
|
3
|
+
* Identity core never hardcodes /auth/login, /agentsam, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IDENTITY_ROUTE_IDS, REQUIRED_AUTH_ROUTE_IDS } from './route-ids.js';
|
|
7
|
+
import { IdentityRoutingError } from './identity-store.js';
|
|
8
|
+
import { sanitizeBrowserNextPath } from '../core/browser-paths.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {{ path: string, auth?: 'required'|'optional'|'public', spa?: boolean, shell?: string, pattern?: string }} RouteProjection
|
|
12
|
+
* @typedef {Record<string, string|RouteProjection>} RouteProjectionMap
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {{ appId: string, routes: RouteProjectionMap }} input
|
|
17
|
+
*/
|
|
18
|
+
export function defineRouteProjection(input) {
|
|
19
|
+
const appId = String(input?.appId || '').trim();
|
|
20
|
+
if (!appId) throw new IdentityRoutingError('AUTH_APP_UNRESOLVED', 'defineRouteProjection requires appId');
|
|
21
|
+
/** @type {Map<string, RouteProjection>} */
|
|
22
|
+
const routes = new Map();
|
|
23
|
+
for (const [routeId, value] of Object.entries(input.routes || {})) {
|
|
24
|
+
const path = typeof value === 'string' ? value : value?.path;
|
|
25
|
+
if (!path || !String(path).startsWith('/')) {
|
|
26
|
+
throw new IdentityRoutingError('AUTH_ROUTE_PROJECTION_INVALID', `Invalid path for ${routeId}`, { appId, routeId });
|
|
27
|
+
}
|
|
28
|
+
routes.set(routeId, Object.freeze({
|
|
29
|
+
path: String(path),
|
|
30
|
+
auth: typeof value === 'object' ? value.auth : undefined,
|
|
31
|
+
spa: typeof value === 'object' ? value.spa : undefined,
|
|
32
|
+
shell: typeof value === 'object' ? value.shell : undefined,
|
|
33
|
+
pattern: typeof value === 'object' ? value.pattern : undefined,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
return Object.freeze({ appId, routes });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {ReturnType<typeof defineRouteProjection>[]} projections
|
|
41
|
+
*/
|
|
42
|
+
export function createRouteRegistry(projections = []) {
|
|
43
|
+
/** @type {Map<string, ReturnType<typeof defineRouteProjection>>} */
|
|
44
|
+
const byApp = new Map();
|
|
45
|
+
for (const p of projections) {
|
|
46
|
+
if (!p?.appId) continue;
|
|
47
|
+
byApp.set(p.appId, p);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return Object.freeze({
|
|
51
|
+
get(appId) {
|
|
52
|
+
return byApp.get(String(appId || '')) || null;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} appId
|
|
57
|
+
* @param {string} routeId
|
|
58
|
+
* @returns {string|null}
|
|
59
|
+
*/
|
|
60
|
+
resolve(appId, routeId) {
|
|
61
|
+
const proj = byApp.get(String(appId || ''));
|
|
62
|
+
if (!proj) return null;
|
|
63
|
+
const entry = proj.routes.get(routeId);
|
|
64
|
+
return entry?.path || null;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Expand parameterized patterns, e.g. /api/oauth/:provider/callback
|
|
69
|
+
* @param {string} appId
|
|
70
|
+
* @param {string} routeId
|
|
71
|
+
* @param {Record<string, string>} params
|
|
72
|
+
*/
|
|
73
|
+
resolvePath(appId, routeId, params = {}) {
|
|
74
|
+
const pattern = this.resolve(appId, routeId);
|
|
75
|
+
if (!pattern) return null;
|
|
76
|
+
return pattern.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (_, key) => {
|
|
77
|
+
const v = params[key];
|
|
78
|
+
if (v == null || v === '') {
|
|
79
|
+
throw new IdentityRoutingError('AUTH_ROUTE_PARAM_MISSING', `Missing :${key} for ${routeId}`, { appId, routeId });
|
|
80
|
+
}
|
|
81
|
+
return encodeURIComponent(String(v));
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* return_to must be same-origin relative and owned by this app's projected mounts/paths.
|
|
87
|
+
* @param {{ appId: string, value?: string|null }} opts
|
|
88
|
+
*/
|
|
89
|
+
resolveReturnTo({ appId, value }) {
|
|
90
|
+
const cleaned = sanitizeBrowserNextPath(value);
|
|
91
|
+
if (!cleaned) return null;
|
|
92
|
+
const pathname = cleaned.split('?')[0];
|
|
93
|
+
const proj = byApp.get(String(appId || ''));
|
|
94
|
+
if (!proj) return null;
|
|
95
|
+
|
|
96
|
+
const authenticated = proj.routes.get(IDENTITY_ROUTE_IDS.APP_AUTHENTICATED)?.path
|
|
97
|
+
|| proj.routes.get(IDENTITY_ROUTE_IDS.APP_HOME)?.path;
|
|
98
|
+
if (authenticated && (pathname === authenticated || pathname.startsWith(`${authenticated}/`))) {
|
|
99
|
+
return cleaned;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const [routeId, entry] of proj.routes) {
|
|
103
|
+
if (routeId.startsWith('identity.')) continue;
|
|
104
|
+
const base = entry.path.replace(/:[^/]+/g, ''); // strip params for prefix mounts
|
|
105
|
+
const mount = base.replace(/\/$/, '') || entry.path;
|
|
106
|
+
if (pathname === mount || pathname.startsWith(`${mount}/`)) return cleaned;
|
|
107
|
+
// Explicit mount keys: app.mount./projects style — also accept path values with auth required
|
|
108
|
+
if (entry.auth === 'required' && (pathname === entry.path || pathname.startsWith(`${entry.path}/`))) {
|
|
109
|
+
return cleaned;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fail closed if auth-capable app is missing required semantic projections.
|
|
117
|
+
* @param {string} appId
|
|
118
|
+
*/
|
|
119
|
+
assertAuthCapable(appId) {
|
|
120
|
+
const proj = byApp.get(String(appId || ''));
|
|
121
|
+
if (!proj) {
|
|
122
|
+
throw new IdentityRoutingError('AUTH_APP_UNRESOLVED', `No route projection for app ${appId}`, { appId });
|
|
123
|
+
}
|
|
124
|
+
for (const routeId of REQUIRED_AUTH_ROUTE_IDS) {
|
|
125
|
+
if (!proj.routes.has(routeId)) {
|
|
126
|
+
throw new IdentityRoutingError(
|
|
127
|
+
'AUTH_ROUTE_MISSING',
|
|
128
|
+
`App ${appId} missing required route projection ${routeId}`,
|
|
129
|
+
{ appId, routeId },
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return proj;
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build a projection map from an agentsam.app.v1 auth+routes manifest section.
|
|
140
|
+
* @param {object} manifest
|
|
141
|
+
*/
|
|
142
|
+
export function projectionFromAppManifest(manifest) {
|
|
143
|
+
const appId = String(manifest?.id || '').trim();
|
|
144
|
+
if (!appId) throw new IdentityRoutingError('AUTH_APP_UNRESOLVED', 'manifest.id required');
|
|
145
|
+
|
|
146
|
+
/** @type {RouteProjectionMap} */
|
|
147
|
+
const routes = {};
|
|
148
|
+
|
|
149
|
+
const rawRoutes = manifest.routes || {};
|
|
150
|
+
for (const [routeId, value] of Object.entries(rawRoutes)) {
|
|
151
|
+
if (routeId === 'entry' || routeId === 'mounts' || routeId === 'paths') continue;
|
|
152
|
+
routes[routeId] = value;
|
|
153
|
+
}
|
|
154
|
+
for (const [routeId, value] of Object.entries(rawRoutes.paths || {})) {
|
|
155
|
+
routes[routeId] = value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const auth = manifest.auth || {};
|
|
159
|
+
const entry = auth.entry || {};
|
|
160
|
+
|
|
161
|
+
function pathFor(ref, legacyPath) {
|
|
162
|
+
if (legacyPath) return legacyPath;
|
|
163
|
+
if (!ref) return null;
|
|
164
|
+
if (typeof ref === 'string' && ref.startsWith('/')) return ref;
|
|
165
|
+
if (typeof ref === 'string' && routes[ref]) {
|
|
166
|
+
const v = routes[ref];
|
|
167
|
+
return typeof v === 'string' ? v : v.path;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const authenticatedPath = pathFor(
|
|
173
|
+
entry.authenticated || IDENTITY_ROUTE_IDS.APP_AUTHENTICATED,
|
|
174
|
+
typeof rawRoutes.entry === 'string' ? rawRoutes.entry : null,
|
|
175
|
+
);
|
|
176
|
+
const loginPath = pathFor(entry.login || IDENTITY_ROUTE_IDS.LOGIN, null);
|
|
177
|
+
const failurePath = pathFor(
|
|
178
|
+
entry.failure || IDENTITY_ROUTE_IDS.RECOVERY || IDENTITY_ROUTE_IDS.LOGIN,
|
|
179
|
+
loginPath,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (authenticatedPath) {
|
|
183
|
+
routes[IDENTITY_ROUTE_IDS.APP_AUTHENTICATED] = {
|
|
184
|
+
path: authenticatedPath,
|
|
185
|
+
auth: 'required',
|
|
186
|
+
spa: true,
|
|
187
|
+
shell: manifest.runtime?.shell
|
|
188
|
+
? `${manifest.runtime.assetRoot || 'dist'}/${manifest.runtime.shell}`.replace(/\/{2,}/g, '/')
|
|
189
|
+
: undefined,
|
|
190
|
+
};
|
|
191
|
+
routes[IDENTITY_ROUTE_IDS.APP_HOME] = authenticatedPath;
|
|
192
|
+
}
|
|
193
|
+
if (loginPath) routes[IDENTITY_ROUTE_IDS.LOGIN] = loginPath;
|
|
194
|
+
if (failurePath) routes[IDENTITY_ROUTE_IDS.RECOVERY] = failurePath;
|
|
195
|
+
|
|
196
|
+
for (const mount of rawRoutes.mounts || []) {
|
|
197
|
+
if (!mount?.path) continue;
|
|
198
|
+
routes[`app.mount.${mount.path}`] = {
|
|
199
|
+
path: mount.path,
|
|
200
|
+
auth: mount.auth || 'required',
|
|
201
|
+
spa: mount.spa,
|
|
202
|
+
shell: mount.shell,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (auth.oauth?.callbackPattern) {
|
|
207
|
+
routes[IDENTITY_ROUTE_IDS.OAUTH_CALLBACK] = auth.oauth.callbackPattern;
|
|
208
|
+
} else if (!routes[IDENTITY_ROUTE_IDS.OAUTH_CALLBACK]) {
|
|
209
|
+
routes[IDENTITY_ROUTE_IDS.OAUTH_CALLBACK] = '/api/oauth/:provider/callback';
|
|
210
|
+
}
|
|
211
|
+
if (auth.oauth?.startPattern) {
|
|
212
|
+
routes[IDENTITY_ROUTE_IDS.OAUTH_START] = auth.oauth.startPattern;
|
|
213
|
+
} else if (!routes[IDENTITY_ROUTE_IDS.OAUTH_START]) {
|
|
214
|
+
routes[IDENTITY_ROUTE_IDS.OAUTH_START] = '/api/oauth/:provider/start';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return defineRouteProjection({ appId, routes });
|
|
218
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable identity = semantic route IDs only.
|
|
3
|
+
* Concrete paths live in host defineRouteProjection / agentsam.app.json.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { IDENTITY_ROUTE_IDS, REQUIRED_AUTH_ROUTE_IDS } from './route-ids.js';
|
|
7
|
+
export {
|
|
8
|
+
defineRouteProjection,
|
|
9
|
+
createRouteRegistry,
|
|
10
|
+
projectionFromAppManifest,
|
|
11
|
+
} from './route-projection.js';
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Same-origin relative path sanitizer — no product-path rewriting.
|
|
3
|
+
* Alias normalization (/login → identity.login projection) is a host concern.
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
6
|
export function sanitizeBrowserNextPath(raw) {
|
|
4
7
|
if (raw == null) return null;
|
|
@@ -13,10 +16,6 @@ export function sanitizeBrowserNextPath(raw) {
|
|
|
13
16
|
pathname = s.slice(0, q);
|
|
14
17
|
search = s.slice(q);
|
|
15
18
|
}
|
|
16
|
-
const lower = pathname.toLowerCase();
|
|
17
|
-
if (lower === '/login' || lower === '/auth/signin') pathname = AUTH_LOGIN_PATH;
|
|
18
|
-
else if (lower === '/signup' || lower === '/auth/register') pathname = AUTH_SIGNUP_PATH;
|
|
19
|
-
|
|
20
19
|
return pathname + search;
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Cookie name + session policy re-exports.
|
|
3
|
+
* Route vocabulary → contracts/routes.js (identity endpoints only).
|
|
4
|
+
* Product homes / mounts → each app's agentsam.app.json (not this package).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { SESSION_POLICY } from './session-policy.js';
|
|
8
|
+
|
|
9
|
+
export { SESSION_POLICY, clampAgentSessionTtl } from './session-policy.js';
|
|
10
|
+
|
|
2
11
|
export const AUTH_COOKIE_NAME = 'session';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export const
|
|
6
|
-
export const DEFAULT_AGENT_SESSION_TTL_SECONDS = 900;
|
|
7
|
-
export const AUTH_LOGIN_PATH = '/auth/login';
|
|
8
|
-
export const AUTH_SIGNUP_PATH = '/auth/signup';
|
|
9
|
-
export const DASHBOARD_AFTER_LOGIN_PATH = '/dashboard/cms';
|
|
12
|
+
|
|
13
|
+
/** @deprecated Prefer SESSION_POLICY.browser.ttlSeconds */
|
|
14
|
+
export const AUTH_SESSION_TTL_SECONDS = SESSION_POLICY.browser.ttlSeconds;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session policy — three concerns stay separate:
|
|
3
|
+
* browser = normal logged-in account session (cookie / auth_sessions)
|
|
4
|
+
* agent = short-lived delegated/runtime grant (execution, terminal, sandbox)
|
|
5
|
+
*
|
|
6
|
+
* Product routing does not live here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const SESSION_POLICY = Object.freeze({
|
|
10
|
+
browser: Object.freeze({
|
|
11
|
+
ttlSeconds: 30 * 24 * 60 * 60,
|
|
12
|
+
}),
|
|
13
|
+
/** Temporary execution authority — not the user's Local Studio login. */
|
|
14
|
+
agent: Object.freeze({
|
|
15
|
+
minTtlSeconds: 60,
|
|
16
|
+
defaultTtlSeconds: 15 * 60,
|
|
17
|
+
maxTtlSeconds: 24 * 60 * 60,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Clamp a requested agent/runtime grant TTL into policy bounds.
|
|
23
|
+
* @param {number|null|undefined} requestedSeconds
|
|
24
|
+
*/
|
|
25
|
+
export function clampAgentSessionTtl(requestedSeconds) {
|
|
26
|
+
const n = Number(requestedSeconds);
|
|
27
|
+
if (!Number.isFinite(n) || n <= 0) return SESSION_POLICY.agent.defaultTtlSeconds;
|
|
28
|
+
return Math.min(
|
|
29
|
+
SESSION_POLICY.agent.maxTtlSeconds,
|
|
30
|
+
Math.max(SESSION_POLICY.agent.minTtlSeconds, Math.floor(n)),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -404,15 +404,15 @@
|
|
|
404
404
|
<button type="submit" id="submitBtn" class="btn-primary">Connect</button>
|
|
405
405
|
|
|
406
406
|
<div class="oauth-stack">
|
|
407
|
-
<a id="googleSignIn" href="/api/oauth/google/start?login=1&return_to=/
|
|
407
|
+
<a id="googleSignIn" href="/api/oauth/google/start?login=1&return_to=/" class="btn-primary" style="background: #ffffff; color: #1E293B; text-decoration: none; padding: 12px 24px; border-radius: 10px; font-weight: 600; font-size: 14px; border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 12px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; gap: 8px;">
|
|
408
408
|
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
|
|
409
409
|
Sign in with Google
|
|
410
410
|
</a>
|
|
411
|
-
<a id="githubSignIn" href="/api/oauth/github/start?next=/
|
|
411
|
+
<a id="githubSignIn" href="/api/oauth/github/start?next=/" class="btn-primary" style="background: rgba(255,255,255,0.1); color: white; text-decoration: none; padding: 12px 24px; border-radius: 10px; font-weight: 600; font-size: 14px; border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 12px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; gap: 8px;">
|
|
412
412
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.43 9.8 8.2 11.38.6.11.82-.26.82-.58 0-.29-.01-1.06-.01-2.08-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.08 1.83 1.23 1.83 1.23 1.07 1.83 2.81 1.3 3.49.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 013-.41c1.02.01 2.05.14 3 .41 2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.8 5.63-5.48 5.92.43.37.81 1.1.81 2.22 0 1.6-.01 2.89-.01 3.28 0 .32.21.69.83.57C20.57 21.79 24 17.3 24 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
413
413
|
Sign in with GitHub
|
|
414
414
|
</a>
|
|
415
|
-
<a id="cloudflareSignIn" href="/api/oauth/cloudflare/start
|
|
415
|
+
<a id="cloudflareSignIn" href="/api/oauth/cloudflare/start" class="btn-primary" style="background: #F6821F; color: white; text-decoration: none; padding: 12px 24px; border-radius: 10px; font-weight: 600; font-size: 14px; border: 1px solid rgba(255,255,255,0.2); box-shadow: 0 4px 12px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: center; gap: 8px;">
|
|
416
416
|
Sign in with Cloudflare
|
|
417
417
|
</a>
|
|
418
418
|
</div>
|
|
@@ -466,13 +466,13 @@
|
|
|
466
466
|
var flow = qs.get('flow');
|
|
467
467
|
var challenge = qs.get('challenge');
|
|
468
468
|
var nextParam = qs.get('next');
|
|
469
|
-
|
|
469
|
+
// No product-home default here — host bounce supplies ?next=; otherwise
|
|
470
|
+
// the Worker resolves against the app manifest entry after auth.
|
|
471
|
+
var oauthDest = '/';
|
|
470
472
|
if (flow === 'oauth' && challenge && challenge.indexOf('olc_') === 0) {
|
|
471
473
|
oauthDest = '/api/oauth/login-challenge/resume?challenge=' + encodeURIComponent(challenge);
|
|
472
474
|
} else if (nextParam && nextParam.startsWith('/') && !nextParam.startsWith('//')) {
|
|
473
|
-
|
|
474
|
-
oauthDest = nextParam;
|
|
475
|
-
}
|
|
475
|
+
oauthDest = nextParam;
|
|
476
476
|
}
|
|
477
477
|
var googleLink = document.getElementById('googleSignIn');
|
|
478
478
|
if (googleLink) {
|
|
@@ -571,7 +571,7 @@
|
|
|
571
571
|
if (res.ok && data.ok) {
|
|
572
572
|
btn.textContent = 'Success';
|
|
573
573
|
btn.style.background = '#10B981';
|
|
574
|
-
var redirectUrl = data.redirect || '/
|
|
574
|
+
var redirectUrl = data.redirect || '/';
|
|
575
575
|
if (typeof window.runGlobeExitTransition === 'function') {
|
|
576
576
|
window.runGlobeExitTransition(redirectUrl);
|
|
577
577
|
} else {
|
|
@@ -803,8 +803,8 @@
|
|
|
803
803
|
(function() {
|
|
804
804
|
var params = new URLSearchParams(window.location.search);
|
|
805
805
|
if (params.get('globe_exit') !== '1') return;
|
|
806
|
-
var next = params.get('next') || '/
|
|
807
|
-
if (!next.startsWith('/') || next.startsWith('//')) next = '/
|
|
806
|
+
var next = params.get('next') || '/';
|
|
807
|
+
if (!next.startsWith('/') || next.startsWith('//')) next = '/';
|
|
808
808
|
document.body.classList.add('oauth-globe-exit');
|
|
809
809
|
var tries = 0;
|
|
810
810
|
var maxFrames = 180;
|
|
@@ -346,11 +346,11 @@
|
|
|
346
346
|
</form>
|
|
347
347
|
|
|
348
348
|
<div style="margin: 20px 0 0; width: 100%; text-align: center; display: flex; flex-direction: column; gap: 12px;">
|
|
349
|
-
<a id="googleSignIn" href="/api/oauth/google/start?login=1&return_to=/
|
|
349
|
+
<a id="googleSignIn" href="/api/oauth/google/start?login=1&return_to=/" class="oauth-btn oauth-google">
|
|
350
350
|
<svg width="18" height="18" viewBox="0 0 24 24"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>
|
|
351
351
|
Sign in with Google
|
|
352
352
|
</a>
|
|
353
|
-
<a id="githubSignIn" href="/api/oauth/github/start?next=/
|
|
353
|
+
<a id="githubSignIn" href="/api/oauth/github/start?next=/" class="oauth-btn oauth-github">
|
|
354
354
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.43 9.8 8.2 11.38.6.11.82-.26.82-.58 0-.29-.01-1.06-.01-2.08-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.08 1.83 1.23 1.83 1.23 1.07 1.83 2.81 1.3 3.49.99.11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 013-.41c1.02.01 2.05.14 3 .41 2.29-1.55 3.3-1.23 3.3-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.8 5.63-5.48 5.92.43.37.81 1.1.81 2.22 0 1.6-.01 2.89-.01 3.28 0 .32.21.69.83.57C20.57 21.79 24 17.3 24 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
355
355
|
Sign in with GitHub
|
|
356
356
|
</a>
|
|
@@ -385,7 +385,7 @@
|
|
|
385
385
|
var backLink = document.getElementById('backToLogin');
|
|
386
386
|
if (backLink) backLink.href = loginHref;
|
|
387
387
|
|
|
388
|
-
var oauthDest = '/
|
|
388
|
+
var oauthDest = '/';
|
|
389
389
|
if (nextParam && nextParam.startsWith('/') && !nextParam.startsWith('//')) {
|
|
390
390
|
oauthDest = nextParam;
|
|
391
391
|
}
|
|
@@ -582,7 +582,7 @@
|
|
|
582
582
|
}
|
|
583
583
|
btn.textContent = 'Success';
|
|
584
584
|
btn.style.background = '#10B981';
|
|
585
|
-
var redirectUrl = data.redirect || '/
|
|
585
|
+
var redirectUrl = data.redirect || '/';
|
|
586
586
|
if (typeof window.runGlobeExitTransition === 'function') {
|
|
587
587
|
window.runGlobeExitTransition(redirectUrl);
|
|
588
588
|
} else {
|
|
@@ -814,8 +814,8 @@
|
|
|
814
814
|
(function() {
|
|
815
815
|
var params = new URLSearchParams(window.location.search);
|
|
816
816
|
if (params.get('globe_exit') !== '1') return;
|
|
817
|
-
var next = params.get('next') || '/
|
|
818
|
-
if (!next.startsWith('/') || next.startsWith('//')) next = '/
|
|
817
|
+
var next = params.get('next') || '/';
|
|
818
|
+
if (!next.startsWith('/') || next.startsWith('//')) next = '/';
|
|
819
819
|
document.body.classList.add('oauth-globe-exit');
|
|
820
820
|
var tries = 0;
|
|
821
821
|
var maxFrames = 180;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<body>
|
|
16
16
|
<main>
|
|
17
17
|
<h1>Post-login (preview stub)</h1>
|
|
18
|
-
<p>In production you would land on <code
|
|
18
|
+
<p>In production you would land on <code>/</code> (or your <code>next</code> URL) after OAuth or password sign-in.</p>
|
|
19
19
|
<p><a href="/auth/login">Back to sign in</a> · <a href="/">Preview hub</a></p>
|
|
20
20
|
</main>
|
|
21
21
|
</body>
|
|
@@ -44,6 +44,27 @@ export {
|
|
|
44
44
|
|
|
45
45
|
export * from './core/index.js';
|
|
46
46
|
export * from './core/constants.js';
|
|
47
|
+
export {
|
|
48
|
+
IDENTITY_ROUTE_IDS,
|
|
49
|
+
REQUIRED_AUTH_ROUTE_IDS,
|
|
50
|
+
defineRouteProjection,
|
|
51
|
+
createRouteRegistry,
|
|
52
|
+
projectionFromAppManifest,
|
|
53
|
+
} from './contracts/routes.js';
|
|
54
|
+
export {
|
|
55
|
+
IDENTITY_STORE_SCHEMA_VERSION,
|
|
56
|
+
IDENTITY_PACKS,
|
|
57
|
+
IdentityRoutingError,
|
|
58
|
+
IdentitySchemaError,
|
|
59
|
+
} from './contracts/identity-store.js';
|
|
60
|
+
export {
|
|
61
|
+
resolvePostAuthDestination,
|
|
62
|
+
beginLoginRedirect,
|
|
63
|
+
resolveMountForPath,
|
|
64
|
+
mountRequiresAuth,
|
|
65
|
+
pathMatchesMount,
|
|
66
|
+
} from './server/post-auth.js';
|
|
67
|
+
export { verifyAppAuthContract, verifyAppPackage } from './app/verify-app.js';
|
|
47
68
|
export { DEFAULT_COMPANY_ID, DEFAULT_COMPANY_SLUG, normalizeCompanyRow } from './contracts/company.js';
|
|
48
69
|
export * from './contracts/auth-config.js';
|
|
49
70
|
export { createPasswordResetService } from './recovery/password-reset.js';
|
|
@@ -52,6 +73,10 @@ export {
|
|
|
52
73
|
registerFinalizeInboundOAuth,
|
|
53
74
|
} from './oauth/callback.js';
|
|
54
75
|
export { createCloudflareD1Adapter } from './adapters/cloudflare-d1/index.js';
|
|
76
|
+
export {
|
|
77
|
+
createSqliteIdentityAdapter,
|
|
78
|
+
applySqliteIdentityMigrations,
|
|
79
|
+
} from './adapters/sqlite/index.js';
|
|
55
80
|
export { createIdentityService } from './server/identity-service.js';
|
|
56
81
|
export { handleIdentityWorkerRequest } from './server/worker-router.js';
|
|
57
82
|
|
|
@@ -21,12 +21,18 @@ npx wrangler secret put IAM_CLIENT_SECRET
|
|
|
21
21
|
|
|
22
22
|
These routes live in the **customer** worker (`handleIdentityWorkerRequest`):
|
|
23
23
|
|
|
24
|
-
1. `/api/oauth/
|
|
25
|
-
|
|
24
|
+
1. `/api/oauth/inneranimalmedia/start` → redirects to IAM AS (requires minted `IAM_CLIENT_*`)
|
|
25
|
+
- Legacy alias: `/api/oauth/iam/start`
|
|
26
|
+
2. `/api/oauth/google/start` → BYOK Google if `GOOGLE_*` set, else platform lane if minted, else 503
|
|
26
27
|
3. `/api/oauth/github/start` → same for GitHub
|
|
27
|
-
4. Callback: `/api/oauth/
|
|
28
|
+
4. Callback: `/api/oauth/inneranimalmedia/callback` (platform lane) or `/api/oauth/{google|github|cloudflare}/callback` (BYOK)
|
|
29
|
+
- Legacy alias: `/api/oauth/iam/callback`
|
|
28
30
|
|
|
29
|
-
Register
|
|
31
|
+
Register platform redirect URI on the IAM client:
|
|
32
|
+
|
|
33
|
+
`https://<customer-host>/api/oauth/inneranimalmedia/callback`
|
|
34
|
+
|
|
35
|
+
(Keep the legacy `/api/oauth/iam/callback` registered during migration.)
|
|
30
36
|
|
|
31
37
|
## IAM authorization server endpoints (issuer)
|
|
32
38
|
|
|
@@ -8,13 +8,17 @@ import { DEFAULT_IAM_ORIGIN, resolveIamOrigin } from '../contracts/auth-config.j
|
|
|
8
8
|
* — SECRET is wrangler secret put only (encryption is law, not luxury).
|
|
9
9
|
*
|
|
10
10
|
* Developer BYOK: GOOGLE_CLIENT_* / GITHUB_CLIENT_* when set for that provider
|
|
11
|
-
* take the /api/oauth/{provider}/start button; otherwise the button uses
|
|
11
|
+
* take the /api/oauth/{provider}/start button; otherwise the button uses the
|
|
12
|
+
* Inner Animal Media platform lane (`inneranimalmedia`).
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
/** @deprecated Use DEFAULT_IAM_ORIGIN from the auth configuration contract. */
|
|
15
16
|
export const DEFAULT_IAM_OAUTH_ISSUER = DEFAULT_IAM_ORIGIN;
|
|
16
17
|
export const IAM_PLATFORM_STATE_PROVIDER = 'iam_platform';
|
|
17
|
-
|
|
18
|
+
/** Canonical platform OAuth callback (protocol id = inneranimalmedia). */
|
|
19
|
+
export const IAM_PLATFORM_CALLBACK_PATH = '/api/oauth/inneranimalmedia/callback';
|
|
20
|
+
/** @deprecated Legacy path — still accepted; prefer IAM_PLATFORM_CALLBACK_PATH. */
|
|
21
|
+
export const IAM_PLATFORM_CALLBACK_PATH_LEGACY = '/api/oauth/iam/callback';
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* @param {Record<string, unknown> | null | undefined} env
|
|
@@ -42,9 +46,9 @@ export function requireIamPlatformCredentials(env) {
|
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* @param {Record<string, unknown> | null | undefined} env
|
|
45
|
-
* @param {'google' | 'github' | 'iam'} provider
|
|
49
|
+
* @param {'google' | 'github' | 'cloudflare' | 'inneranimalmedia' | 'iam'} provider
|
|
46
50
|
* @returns {{
|
|
47
|
-
* lane: 'iam_platform' | 'byok_google' | 'byok_github',
|
|
51
|
+
* lane: 'iam_platform' | 'byok_google' | 'byok_github' | 'byok_cloudflare',
|
|
48
52
|
* clientId: string,
|
|
49
53
|
* clientSecret: string,
|
|
50
54
|
* origin?: string,
|
|
@@ -53,7 +57,10 @@ export function requireIamPlatformCredentials(env) {
|
|
|
53
57
|
* } | null}
|
|
54
58
|
*/
|
|
55
59
|
export function resolveOAuthCredentialLane(env, provider) {
|
|
56
|
-
|
|
60
|
+
const key = String(provider || '').trim().toLowerCase();
|
|
61
|
+
|
|
62
|
+
// Protocol/selection id is inneranimalmedia; `iam` remains a legacy alias.
|
|
63
|
+
if (key === 'inneranimalmedia' || key === 'iam') {
|
|
57
64
|
const iam = resolveIamPlatformCredentials(env);
|
|
58
65
|
if (!iam) return null;
|
|
59
66
|
return {
|
|
@@ -62,27 +69,27 @@ export function resolveOAuthCredentialLane(env, provider) {
|
|
|
62
69
|
clientSecret: iam.clientSecret,
|
|
63
70
|
origin: iam.origin,
|
|
64
71
|
issuer: iam.issuer,
|
|
65
|
-
provider: '
|
|
72
|
+
provider: 'inneranimalmedia',
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
if (
|
|
76
|
+
if (key === 'google') {
|
|
70
77
|
const clientId = String(env?.GOOGLE_CLIENT_ID || '').trim();
|
|
71
78
|
const clientSecret = String(env?.GOOGLE_CLIENT_SECRET || '').trim();
|
|
72
79
|
if (clientId && clientSecret) {
|
|
73
|
-
return { lane: 'byok_google', clientId, clientSecret, provider };
|
|
80
|
+
return { lane: 'byok_google', clientId, clientSecret, provider: 'google' };
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
if (
|
|
84
|
+
if (key === 'github') {
|
|
78
85
|
const clientId = String(env?.GITHUB_CLIENT_ID || '').trim();
|
|
79
86
|
const clientSecret = String(env?.GITHUB_CLIENT_SECRET || '').trim();
|
|
80
87
|
if (clientId && clientSecret) {
|
|
81
|
-
return { lane: 'byok_github', clientId, clientSecret, provider };
|
|
88
|
+
return { lane: 'byok_github', clientId, clientSecret, provider: 'github' };
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
91
|
|
|
85
|
-
if (
|
|
92
|
+
if (key === 'cloudflare') {
|
|
86
93
|
// Reuses the same Worker secrets as the Local Studio Cloudflare resource
|
|
87
94
|
// connector (packages/connectors/cloudflare). clientSecret may be empty
|
|
88
95
|
// if that client is configured as PKCE-only (Token Authentication
|
|
@@ -90,7 +97,7 @@ export function resolveOAuthCredentialLane(env, provider) {
|
|
|
90
97
|
const clientId = String(env?.CLOUDFLARE_OAUTH_CLIENT_ID || '').trim();
|
|
91
98
|
const clientSecret = String(env?.CLOUDFLARE_OAUTH_CLIENT_SECRET || '').trim();
|
|
92
99
|
if (clientId) {
|
|
93
|
-
return { lane: 'byok_cloudflare', clientId, clientSecret, provider };
|
|
100
|
+
return { lane: 'byok_cloudflare', clientId, clientSecret, provider: 'cloudflare' };
|
|
94
101
|
}
|
|
95
102
|
return null;
|
|
96
103
|
}
|
|
@@ -104,7 +111,7 @@ export function resolveOAuthCredentialLane(env, provider) {
|
|
|
104
111
|
clientSecret: iam.clientSecret,
|
|
105
112
|
origin: iam.origin,
|
|
106
113
|
issuer: iam.issuer,
|
|
107
|
-
provider,
|
|
114
|
+
provider: 'inneranimalmedia',
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
117
|
|
|
@@ -51,7 +51,7 @@ export function createFinalizeInboundOAuth(ports) {
|
|
|
51
51
|
? String(input.supabaseUserId).trim()
|
|
52
52
|
: null;
|
|
53
53
|
const source = String(input?.source || `${provider}_oauth`).trim();
|
|
54
|
-
const pageContext = String(input?.pageContext || '/
|
|
54
|
+
const pageContext = String(input?.pageContext || '/').trim();
|
|
55
55
|
|
|
56
56
|
if (!ports.hasDatabase(env) || !oauthEmail || !provider || !providerUid) {
|
|
57
57
|
return /** @type {InboundOAuthFailure} */ ({ ok: false, error: 'provision_failed' });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IDENTITY_ROUTE_IDS } from '../contracts/route-ids.js';
|
|
2
2
|
import {
|
|
3
3
|
IAM_PLATFORM_CALLBACK_PATH,
|
|
4
4
|
IAM_PLATFORM_STATE_PROVIDER,
|
|
@@ -38,6 +38,7 @@ export async function iamPlatformOAuthStart(request, env, adapter, identity) {
|
|
|
38
38
|
provider: IAM_PLATFORM_STATE_PROVIDER,
|
|
39
39
|
codeVerifier,
|
|
40
40
|
redirectTo,
|
|
41
|
+
appId: identity?.app?.id || null,
|
|
41
42
|
});
|
|
42
43
|
|
|
43
44
|
const redirectUri = `${url.origin}${IAM_PLATFORM_CALLBACK_PATH}`;
|
|
@@ -67,15 +68,15 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
|
|
|
67
68
|
const err = url.searchParams.get('error');
|
|
68
69
|
|
|
69
70
|
if (err || !code || !state) {
|
|
70
|
-
return Response.redirect(`${url.origin}${
|
|
71
|
+
return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=oauth_failed`, 302);
|
|
71
72
|
}
|
|
72
73
|
if (!creds) {
|
|
73
|
-
return Response.redirect(`${url.origin}${
|
|
74
|
+
return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=iam_oauth_not_configured`, 302);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
const saved = await adapter.consumeOAuthState(state);
|
|
77
78
|
if (!saved || saved.provider !== IAM_PLATFORM_STATE_PROVIDER) {
|
|
78
|
-
return Response.redirect(`${url.origin}${
|
|
79
|
+
return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=state_mismatch`, 302);
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
const redirectUri = `${url.origin}${IAM_PLATFORM_CALLBACK_PATH}`;
|
|
@@ -88,7 +89,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
|
|
|
88
89
|
redirectUri,
|
|
89
90
|
});
|
|
90
91
|
if (!token?.access_token) {
|
|
91
|
-
return Response.redirect(`${url.origin}${
|
|
92
|
+
return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=token_exchange_failed`, 302);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
const profile = await fetchIamProfile({
|
|
@@ -97,7 +98,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
|
|
|
97
98
|
});
|
|
98
99
|
const normalized = profile ? normalizeIamIdentity(profile) : null;
|
|
99
100
|
if (!normalized?.subject || !normalized?.email) {
|
|
100
|
-
return Response.redirect(`${url.origin}${
|
|
101
|
+
return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=userinfo_failed`, 302);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
const result = await identity.provisionOAuthUser({
|
|
@@ -109,7 +110,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
|
|
|
109
110
|
|
|
110
111
|
const redirectTo = identity.resolvePostLoginPath(saved.redirect_to);
|
|
111
112
|
const res = identity.buildLoginSuccessResponse(request, result.sessionId, redirectTo);
|
|
112
|
-
const globeUrl = `${url.origin}${
|
|
113
|
+
const globeUrl = `${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?globe_exit=1&next=${encodeURIComponent(redirectTo)}`;
|
|
113
114
|
return new Response(null, {
|
|
114
115
|
status: 302,
|
|
115
116
|
headers: {
|