@ima-jin/config 0.8.0 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +125 -24
- package/dist/index.d.cts +152 -10
- package/dist/index.d.ts +152 -10
- package/dist/index.js +119 -24
- package/package.json +2 -5
- package/src/base-path.ts +6 -6
- package/src/brand.ts +12 -0
- package/src/cors.ts +1 -1
- package/src/dashboard-redirect-middleware.ts +56 -0
- package/src/forest-config.ts +51 -0
- package/src/handles.ts +1 -1
- package/src/index.ts +11 -0
- package/src/node-self.ts +45 -0
- package/src/registry-service-url.ts +54 -0
- package/src/services.ts +18 -2
- package/src/session.ts +28 -1
package/src/index.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { isAllowedOrigin, corsHeaders, corsOptions, withCors, validateOrigin, ALLOWED_FRAME_ORIGINS } from "./cors";
|
|
2
|
+
export { standaloneDashboardMiddleware } from "./dashboard-redirect-middleware";
|
|
3
|
+
export type { StandaloneDashboardMiddlewareOptions } from "./dashboard-redirect-middleware";
|
|
2
4
|
export { tier1Headers, tier2Headers, tier3Headers } from './security-headers';
|
|
5
|
+
export { APP_DISPLAY_NAME } from './brand';
|
|
3
6
|
|
|
4
7
|
export {
|
|
5
8
|
SERVICES,
|
|
@@ -31,6 +34,14 @@ export { apiFetch, apiUrl } from "./base-path";
|
|
|
31
34
|
|
|
32
35
|
export { rateLimit, getClientIP } from "./rate-limit";
|
|
33
36
|
|
|
37
|
+
export { getNodeSelf } from "./node-self";
|
|
38
|
+
export type { NodeSelfInfo } from "./node-self";
|
|
39
|
+
|
|
40
|
+
export { registryServiceUrl, hasRegistryServiceUrl } from "./registry-service-url";
|
|
41
|
+
|
|
42
|
+
export { getForestScopeConfig } from "./forest-config";
|
|
43
|
+
export type { ForestScopeConfig } from "./forest-config";
|
|
44
|
+
|
|
34
45
|
export {
|
|
35
46
|
HANDLE_PATTERN,
|
|
36
47
|
HANDLE_INPUT_PATTERN,
|
package/src/node-self.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { registryServiceUrl } from './registry-service-url';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal public node metadata returned by GET /registry/api/node/self.
|
|
5
|
+
* Mirrors the subset of `relay.relay_config` the registry service exposes
|
|
6
|
+
* publicly — never the private config (profile artifact JWS, etc).
|
|
7
|
+
*/
|
|
8
|
+
export interface NodeSelfInfo {
|
|
9
|
+
/** This node's own DID (did:imajin:...). */
|
|
10
|
+
did: string;
|
|
11
|
+
/** DID of the human/entity operating this node, if configured. */
|
|
12
|
+
nodeOperatorDid: string | null;
|
|
13
|
+
/** Node fee, in basis points, applied to .fair settlements on this node. */
|
|
14
|
+
nodeFeeBps: number | null;
|
|
15
|
+
/** Buyer credit, in basis points, applied to .fair settlements on this node. */
|
|
16
|
+
buyerCreditBps: number | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fetch this node's public identity + .fair fee config from the registry
|
|
21
|
+
* service (`GET /registry/api/node/self`, #2000).
|
|
22
|
+
*
|
|
23
|
+
* Replaces raw `relay.relay_config` SQL reads that were duplicated across
|
|
24
|
+
* coffee, learn, events, and market (audit item 8 of #1983). Returns null on
|
|
25
|
+
* any failure — network error, non-2xx response (e.g. 503 when the node
|
|
26
|
+
* identity hasn't been bootstrapped yet) — so callers can fall back to
|
|
27
|
+
* defaults exactly as they did when the raw SQL row was missing. A non-2xx
|
|
28
|
+
* response is logged with the URL actually hit (no secrets) so a
|
|
29
|
+
* misconfigured REGISTRY_SERVICE_URL prefix is visible instead of failing
|
|
30
|
+
* silently (#2046).
|
|
31
|
+
*/
|
|
32
|
+
export async function getNodeSelf(): Promise<NodeSelfInfo | null> {
|
|
33
|
+
const url = `${registryServiceUrl()}/api/node/self`;
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(url);
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
console.warn(`[config] getNodeSelf: registry returned ${res.status} for ${url} — check REGISTRY_SERVICE_URL`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return (await res.json()) as NodeSelfInfo;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.warn(`[config] getNodeSelf: fetch failed for ${url} — ${err instanceof Error ? err.message : String(err)}`);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single resolver for the registry service's base URL (#2061 — kernel was
|
|
3
|
+
* the last `REGISTRY_URL` holdout after #2048 established the
|
|
4
|
+
* `*_SERVICE_URL` convention).
|
|
5
|
+
*
|
|
6
|
+
* The returned value always includes the `/registry` path prefix, like
|
|
7
|
+
* every other `*_SERVICE_URL` (#2046) — callers append only the endpoint
|
|
8
|
+
* path (e.g. `/api/node/self`, `/relay/operations`).
|
|
9
|
+
*
|
|
10
|
+
* Precedence:
|
|
11
|
+
* 1. `REGISTRY_SERVICE_URL` — the current convention.
|
|
12
|
+
* 2. `REGISTRY_URL` — deprecated kernel-only legacy name. Used if set,
|
|
13
|
+
* but logs a one-time deprecation warning per process.
|
|
14
|
+
* 3. `http://localhost:${PORT || 3000}/registry` — the mode-aware
|
|
15
|
+
* fallback (PORT is 3000 in dev, 7000 in prod) that
|
|
16
|
+
* `relay-well-known.ts` already used before this consolidation.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
let warnedAboutLegacyRegistryUrl = false;
|
|
20
|
+
|
|
21
|
+
export function registryServiceUrl(): string {
|
|
22
|
+
if (process.env.REGISTRY_SERVICE_URL) return process.env.REGISTRY_SERVICE_URL;
|
|
23
|
+
|
|
24
|
+
if (process.env.REGISTRY_URL) {
|
|
25
|
+
if (!warnedAboutLegacyRegistryUrl) {
|
|
26
|
+
console.warn('REGISTRY_URL is deprecated — set REGISTRY_SERVICE_URL');
|
|
27
|
+
warnedAboutLegacyRegistryUrl = true;
|
|
28
|
+
}
|
|
29
|
+
return process.env.REGISTRY_URL;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return `http://localhost:${process.env.PORT || 3000}/registry`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* True when the registry URL is explicitly configured via either the
|
|
37
|
+
* current (`REGISTRY_SERVICE_URL`) or deprecated (`REGISTRY_URL`) env var.
|
|
38
|
+
*
|
|
39
|
+
* `registryServiceUrl()` always returns a value (it falls back to a
|
|
40
|
+
* localhost guess), so callers that previously guarded on
|
|
41
|
+
* `!process.env.REGISTRY_URL` to skip/no-op when the registry isn't
|
|
42
|
+
* configured should check this instead.
|
|
43
|
+
*/
|
|
44
|
+
export function hasRegistryServiceUrl(): boolean {
|
|
45
|
+
return Boolean(process.env.REGISTRY_SERVICE_URL || process.env.REGISTRY_URL);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Test-only: resets the one-time deprecation warning latch so each test
|
|
50
|
+
* can assert the warning fires exactly once for its own scenario.
|
|
51
|
+
*/
|
|
52
|
+
export function __resetRegistryServiceUrlWarningForTests(): void {
|
|
53
|
+
warnedAboutLegacyRegistryUrl = false;
|
|
54
|
+
}
|
package/src/services.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical service manifest —
|
|
2
|
+
* Canonical service manifest — infra/build-time topology for all Imajin
|
|
3
|
+
* services (dev/prod ports, Postgres schema, deployment tier, Caddy/pm2
|
|
4
|
+
* routing, docs sync, `/registry/api/specs`). These consumers run without a
|
|
5
|
+
* live identity/DB context (build scripts, edge config, port-based
|
|
6
|
+
* routing), so this stays a static, synchronously-importable array.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
8
|
+
* NOT the nav authority (#2425): which apps an authenticated identity sees
|
|
9
|
+
* in the kernel hub (auth submenu, launcher, home tiles) is now derived
|
|
10
|
+
* from `registry.apps` (#1990) via `apps/kernel/src/lib/kernel/app-nav.ts`,
|
|
11
|
+
* not from this file — a disabled/pruned app disappears from nav by
|
|
12
|
+
* flipping registry state, independent of whether it still has a row here
|
|
13
|
+
* for port/Caddy purposes. Consumers: registry specs, Caddy config, pm2
|
|
14
|
+
* ecosystem, docs sync.
|
|
5
15
|
*/
|
|
6
16
|
|
|
7
17
|
export type ServiceTier = "core" | "imajin";
|
|
@@ -46,6 +56,11 @@ export const SERVICES: readonly ServiceDefinition[] = [
|
|
|
46
56
|
{ name: "media", label: "Media", icon: "📁", description: "Files, images, and .fair attribution", devPort: 3000, prodPort: 7000, schema: "media", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
47
57
|
{ name: "registry", label: "Registry", icon: "📡", description: "Service registry and DFOS relay", devPort: 3000, prodPort: 7000, schema: "registry", tier: "core", visibility: "public", category: "kernel" },
|
|
48
58
|
{ name: "notify", label: "Notify", icon: "🔔", description: "Notifications and preferences", devPort: 3000, prodPort: 7000, schema: "notify", tier: "core", visibility: "internal", category: "kernel" },
|
|
59
|
+
{ name: "usage", label: "Usage", icon: "📊", description: "Inference cost ledger — incurred, billed, drift, signed rollup", devPort: 3000, prodPort: 7000, schema: "usage", tier: "core", visibility: "authenticated", category: "kernel" },
|
|
60
|
+
{ name: "calendar", label: "Calendar", icon: "📅", description: "Temporal entries — availability, meetings, events, and reminders", devPort: 3000, prodPort: 7000, schema: null, tier: "core", visibility: "authenticated", category: "kernel" },
|
|
61
|
+
{ name: "jin", label: "Jin", icon: "🧭", description: "Operator dashboard — signed approvals, proposals, and usage feed", devPort: 3000, prodPort: 7000, schema: "operator", tier: "core", visibility: "internal", category: "kernel" },
|
|
62
|
+
{ name: "google", label: "Google Workspace", icon: "📨", description: "Gmail/Calendar/Drive/Meet connector — OAuth2 auth-code, sealed refresh token", devPort: 3000, prodPort: 7000, schema: null, tier: "core", visibility: "authenticated", category: "kernel" },
|
|
63
|
+
{ name: "vault", label: "Vault", icon: "🔐", description: "Sealed secrets, delegation grants, and vault-minted service credentials", devPort: 3000, prodPort: 7000, schema: null, tier: "core", visibility: "internal", category: "kernel" },
|
|
49
64
|
|
|
50
65
|
// Core apps — separate processes
|
|
51
66
|
{ name: "events", label: "Events", icon: "🎫", description: "Event creation, ticketing, and management", devPort: 3006, prodPort: 7006, schema: "events", tier: "core", visibility: "public", category: "core" },
|
|
@@ -64,6 +79,7 @@ export const SERVICES: readonly ServiceDefinition[] = [
|
|
|
64
79
|
|
|
65
80
|
// Daemon processes — not web apps, no HTTP port exposed
|
|
66
81
|
{ name: 'broker-agent', label: 'Broker Agent', icon: '🤖', description: 'Telegram broker agent — conversational surface for social coordination (#1101)', devPort: 0, prodPort: 0, schema: null, tier: 'core', visibility: 'internal', category: 'infrastructure' },
|
|
82
|
+
{ name: 'corpus', label: 'Corpus', icon: '🔎', description: 'Per-DID corpus indexing and BM25 search service (#1737)', devPort: 8013, prodPort: 8013, schema: null, tier: 'core', visibility: 'internal', category: 'infrastructure' },
|
|
67
83
|
|
|
68
84
|
// Connected apps (separate repos) will use the plugin architecture (#249).
|
|
69
85
|
// Not included here — they authenticate via delegated sessions, not the monorepo manifest.
|
package/src/session.ts
CHANGED
|
@@ -27,15 +27,42 @@ function isLocalhost(): boolean {
|
|
|
27
27
|
return prefix.includes("localhost");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Cookie domain scope (#1069 Phase 1).
|
|
32
|
+
*
|
|
33
|
+
* `SESSION_COOKIE_SCOPE` controls whether the deployed session cookie is a
|
|
34
|
+
* shared parent-domain cookie or a host-only cookie:
|
|
35
|
+
* - unset / "domain" (default): current behavior — `domain: ".imajin.ai"`,
|
|
36
|
+
* readable by every `*.imajin.ai` subdomain. Nothing changes unless this
|
|
37
|
+
* is explicitly set.
|
|
38
|
+
* - "host": omit the `domain` attribute entirely, so the cookie is scoped
|
|
39
|
+
* to the exact host that set it and is NOT sent to other subdomains.
|
|
40
|
+
* Apps that relied on reading the shared cookie must adopt scoped app
|
|
41
|
+
* tokens first (see `requireSessionOrAppToken` in `@ima-jin/auth` and
|
|
42
|
+
* `docs/security/cookie-isolation.md`) before this is flipped for real.
|
|
43
|
+
*
|
|
44
|
+
* This flag is a primitive for the staged rollout described in
|
|
45
|
+
* `docs/security/cookie-isolation.md` — flipping it is a separate, later
|
|
46
|
+
* phase, not part of shipping the primitive itself.
|
|
47
|
+
*/
|
|
48
|
+
function isHostScoped(): boolean {
|
|
49
|
+
if (typeof process === "undefined") return false;
|
|
50
|
+
return process.env.SESSION_COOKIE_SCOPE === "host";
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/** Cookie options for cross-subdomain sessions.
|
|
31
54
|
* When called with no argument, auto-detects from IMAJIN_ENV (same logic as getSessionCookieName).
|
|
32
55
|
* Accepts optional "dev" | "prod" override for explicit control.
|
|
33
56
|
*
|
|
34
57
|
* Localhost-aware: when SERVICE_PREFIX contains "localhost", uses
|
|
35
58
|
* settings compatible with HTTP on localhost (no domain, not secure, lax sameSite).
|
|
59
|
+
*
|
|
60
|
+
* See {@link isHostScoped} for the `SESSION_COOKIE_SCOPE=host` opt-in that
|
|
61
|
+
* narrows the deployed cookie to host-only instead of `.imajin.ai`.
|
|
36
62
|
*/
|
|
37
63
|
export function getSessionCookieOptions(env?: "dev" | "prod") {
|
|
38
64
|
const local = isLocalhost();
|
|
65
|
+
const omitDomain = local || isHostScoped();
|
|
39
66
|
return {
|
|
40
67
|
name: getSessionCookieName(env),
|
|
41
68
|
options: {
|
|
@@ -43,7 +70,7 @@ export function getSessionCookieOptions(env?: "dev" | "prod") {
|
|
|
43
70
|
secure: !local,
|
|
44
71
|
sameSite: local ? ("lax" as const) : ("none" as const),
|
|
45
72
|
path: "/",
|
|
46
|
-
...(
|
|
73
|
+
...(omitDomain ? {} : { domain: ".imajin.ai" }),
|
|
47
74
|
maxAge: 60 * 60 * 24, // 24 hours
|
|
48
75
|
},
|
|
49
76
|
};
|